diff --git a/deploy/install.sh b/deploy/install.sh index ef4e75171a..4eeee3ee67 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -228,7 +228,7 @@ wait_for_containers_start() { # The while loop is important because for-loops don't work for dynamic values while [[ $timeout -gt 0 ]]; do - status_code="$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3301/api/v1/services/list || true)" + status_code="$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:3301/api/v1/health?live=1" || true)" if [[ status_code -eq 200 ]]; then break else diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index b0fbef4d0f..a1b2ceabab 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -3640,3 +3640,13 @@ func (r *ClickHouseReader) QueryDashboardVars(ctx context.Context, query string) } return &result, nil } + +func (r *ClickHouseReader) CheckClickHouse(ctx context.Context) error { + rows, err := r.db.Query(ctx, "SELECT 1") + if err != nil { + return err + } + defer rows.Close() + + return nil +} diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 58a060649a..8d49dbb0ad 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -351,7 +351,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) { router.HandleFunc("/api/v1/feedback", OpenAccess(aH.submitFeedback)).Methods(http.MethodPost) // router.HandleFunc("/api/v1/get_percentiles", aH.getApplicationPercentiles).Methods(http.MethodGet) router.HandleFunc("/api/v1/services", ViewAccess(aH.getServices)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/services/list", aH.getServicesList).Methods(http.MethodGet) + router.HandleFunc("/api/v1/services/list", ViewAccess(aH.getServicesList)).Methods(http.MethodGet) router.HandleFunc("/api/v1/service/overview", ViewAccess(aH.getServiceOverview)).Methods(http.MethodPost) router.HandleFunc("/api/v1/service/top_operations", ViewAccess(aH.getTopOperations)).Methods(http.MethodPost) router.HandleFunc("/api/v1/service/top_level_operations", ViewAccess(aH.getServicesTopLevelOps)).Methods(http.MethodPost) @@ -364,6 +364,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) { router.HandleFunc("/api/v1/version", OpenAccess(aH.getVersion)).Methods(http.MethodGet) router.HandleFunc("/api/v1/featureFlags", OpenAccess(aH.getFeatureFlags)).Methods(http.MethodGet) router.HandleFunc("/api/v1/configs", OpenAccess(aH.getConfigs)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/health", OpenAccess(aH.getHealth)).Methods(http.MethodGet) router.HandleFunc("/api/v1/getSpanFilters", ViewAccess(aH.getSpanFilters)).Methods(http.MethodPost) router.HandleFunc("/api/v1/getTagFilters", ViewAccess(aH.getTagFilters)).Methods(http.MethodPost) @@ -1671,6 +1672,22 @@ func (aH *APIHandler) getConfigs(w http.ResponseWriter, r *http.Request) { aH.Respond(w, configs) } +// getHealth is used to check the health of the service. +// 'live' query param can be used to check liveliness of +// the service by checking the database connection. +func (aH *APIHandler) getHealth(w http.ResponseWriter, r *http.Request) { + _, ok := r.URL.Query()["live"] + if ok { + err := aH.reader.CheckClickHouse(r.Context()) + if err != nil { + aH.HandleError(w, err, http.StatusServiceUnavailable) + return + } + } + + aH.WriteJSON(w, r, map[string]string{"status": "ok"}) +} + // inviteUser is used to invite a user. It is used by an admin api. func (aH *APIHandler) inviteUser(w http.ResponseWriter, r *http.Request) { req, err := parseInviteRequest(r) diff --git a/pkg/query-service/interfaces/interface.go b/pkg/query-service/interfaces/interface.go index a551e4b7f8..1a98e5d1d7 100644 --- a/pkg/query-service/interfaces/interface.go +++ b/pkg/query-service/interfaces/interface.go @@ -77,4 +77,5 @@ type Reader interface { GetFanoutStorage() *storage.Storage QueryDashboardVars(ctx context.Context, query string) (*model.DashboardVar, error) + CheckClickHouse(ctx context.Context) error }