Introduce /health endpoint
parent
d99f7720ee
commit
5f5a8c2ee5
|
@ -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))
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.`
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue