Introduce /health endpoint

pull/3118/head
Andrew Watkins 2018-04-03 15:58:33 -07:00
parent d99f7720ee
commit 5f5a8c2ee5
6 changed files with 94 additions and 24 deletions

View File

@ -155,6 +155,7 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
router.GET("/chronograf/v1/sources/:id", EnsureViewer(service.SourcesID))
router.PATCH("/chronograf/v1/sources/:id", EnsureEditor(service.UpdateSource))
router.DELETE("/chronograf/v1/sources/:id", EnsureEditor(service.RemoveSource))
router.GET("/chronograf/v1/sources/:id/health", EnsureViewer(service.SourceHealth))
// IFQL
router.GET("/chronograf/v1/ifql", EnsureViewer(service.IFQL))

View File

@ -25,6 +25,7 @@ type sourceLinks struct {
Roles string `json:"roles,omitempty"` // URL for all users associated with this source
Databases string `json:"databases"` // URL for the databases contained within this source
Annotations string `json:"annotations"` // URL for the annotations of this source
Health string `json:"health"` // URL for source health
}
type sourceResponse struct {
@ -55,6 +56,7 @@ func newSourceResponse(src chronograf.Source) sourceResponse {
Users: fmt.Sprintf("%s/%d/users", httpAPISrcs, src.ID),
Databases: fmt.Sprintf("%s/%d/dbs", httpAPISrcs, src.ID),
Annotations: fmt.Sprintf("%s/%d/annotations", httpAPISrcs, src.ID),
Health: fmt.Sprintf("%s/%d/health", httpAPISrcs, src.ID),
},
}
@ -192,6 +194,38 @@ func (s *Service) RemoveSource(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
// SourceHealth determines if the tsdb is running
func (s *Service) SourceHealth(w http.ResponseWriter, r *http.Request) {
id, err := paramID("id", r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
return
}
ctx := r.Context()
src, err := s.Store.Sources(ctx).Get(ctx, id)
if err != nil {
notFound(w, id, s.Logger)
return
}
cli := &influx.Client{
Logger: s.Logger,
}
if err := cli.Connect(ctx, &src); err != nil {
Error(w, http.StatusBadRequest, "Error contacting source", s.Logger)
return
}
if err := cli.Ping(ctx); err != nil {
Error(w, http.StatusBadRequest, "Error contacting source", s.Logger)
return
}
w.WriteHeader(http.StatusOK)
}
// removeSrcsKapa will remove all kapacitors and kapacitor rules from the stores.
// However, it will not remove the kapacitor tickscript from kapacitor itself.
func (s *Service) removeSrcsKapa(ctx context.Context, srcID int) error {

View File

@ -387,6 +387,39 @@
}
}
},
"/sources/{id}/health": {
"get": {
"tags": ["sources"],
"summary": "Health check for source",
"description": "Returns if the tsdb source can be contacted",
"parameters": [
{
"name": "id",
"in": "path",
"type": "string",
"description": "ID of the data source",
"required": true
}
],
"responses": {
"200": {
"description": "Source was able to be contacted"
},
"404": {
"description": "Source could not be contacted",
"schema": {
"$ref": "#/definitions/Error"
}
},
"default": {
"description": "A processing or an unexpected error.",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
}
},
"/sources/{id}/permissions": {
"get": {
"tags": ["sources", "users"],
@ -3685,7 +3718,8 @@
"queries": "/chronograf/v1/sources/4/queries",
"permissions": "/chronograf/v1/sources/4/permissions",
"users": "/chronograf/v1/sources/4/users",
"roles": "/chronograf/v1/sources/4/roles"
"roles": "/chronograf/v1/sources/4/roles",
"health": "/chronograf/v1/sources/4/health"
}
},
"required": ["url"],
@ -3792,6 +3826,11 @@
"description":
"Optional path to the roles endpoint IFF it is supported on this source",
"format": "url"
},
"health": {
"type": "string",
"description": "Path to determine if source is healthy",
"format": "url"
}
}
}

View File

@ -11,7 +11,7 @@ import {
ADMIN_ROLE,
} from 'src/auth/Authorized'
import {showDatabases} from 'src/shared/apis/metaQuery'
import {getSourceHealth} from 'src/sources/apis'
import {getSourcesAsync} from 'src/shared/actions/sources'
import {errorThrown as errorThrownAction} from 'src/shared/actions/errors'
@ -87,16 +87,15 @@ class CheckSources extends Component<Props, State> {
}
public shouldComponentUpdate(nextProps) {
const {auth: {isUsingAuth, me}} = nextProps
// don't update this component if currentOrganization is what has changed,
// or else the app will try to call showDatabases in componentWillUpdate,
// which will fail unless sources have been refreshed
const {location} = nextProps
if (
isUsingAuth &&
me.currentOrganization.id !== this.props.auth.me.currentOrganization.id
!this.state.isFetching &&
this.props.location.pathname === location.pathname
) {
return false
}
return true
}
@ -109,7 +108,6 @@ class CheckSources extends Component<Props, State> {
sources,
auth: {isUsingAuth, me, me: {organizations = [], currentOrganization}},
notify,
getSources,
} = nextProps
const {isFetching} = nextState
const source = sources.find(s => s.id === params.sourceID)
@ -169,22 +167,10 @@ class CheckSources extends Component<Props, State> {
}
if (!isFetching && !location.pathname.includes('/manage-sources')) {
// Do simple query to proxy to see if the source is up.
try {
// the guard around currentOrganization prevents this showDatabases
// invocation since sources haven't been refreshed yet
await showDatabases(source.links.proxy)
await getSourceHealth(source.links.health)
} catch (error) {
try {
const newSources = await getSources()
if (newSources.length) {
errorThrown(error, copy.notifySourceNoLongerAvailable(source.name))
} else {
errorThrown(error, copy.notifyNoSourcesAvailable(source.name))
}
} catch (error2) {
errorThrown(error2, copy.notifyUnableToRetrieveSources())
}
errorThrown(error, copy.notifySourceNoLongerAvailable(source.name))
}
}
}

View File

@ -133,7 +133,7 @@ export const notifySourceDeleteFailed = sourceName => ({
})
export const notifySourceNoLongerAvailable = sourceName =>
`Source ${sourceName} is no longer available. Successfully connected to another source.`
`Source ${sourceName} is no longer available. Please ensure InfluxDB is running.`
export const notifyNoSourcesAvailable = sourceName =>
`Unable to connect to source ${sourceName}. No other sources available.`

View File

@ -0,0 +1,10 @@
import AJAX from 'src/utils/ajax'
export const getSourceHealth = async (url: string) => {
try {
await AJAX({url})
} catch (error) {
console.error(`Unable to contact source ${url}`, error)
throw error
}
}