/* * Copyright (C) 2017-2019 HERE Europe B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 * License-Filename: LICENSE */ package com.here.xyz.hub.rest.health; import static com.here.xyz.hub.rest.Api.HeaderValues.APPLICATION_JSON; import static io.netty.handler.codec.http.HttpResponseStatus.OK; import com.here.xyz.hub.Service; import com.here.xyz.hub.rest.Api; import com.here.xyz.hub.rest.admin.Node; import com.here.xyz.hub.util.health.Config; import com.here.xyz.hub.util.health.MainHealthCheck; import com.here.xyz.hub.util.health.checks.ExecutableCheck; import com.here.xyz.hub.util.health.checks.JDBCHealthCheck; import com.here.xyz.hub.util.health.checks.RedisHealthCheck; import com.here.xyz.hub.util.health.checks.RemoteFunctionHealthAggregator; import com.here.xyz.hub.util.health.schema.Reporter; import com.here.xyz.hub.util.health.schema.Response; import io.vertx.core.Vertx; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpMethod; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; import java.net.URI; import java.net.URISyntaxException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class HealthApi extends Api { private static final Logger logger = LogManager.getLogger(); public static final String MAIN_HEALTCHECK_ENDPOINT = "/hub/"; private static final URI NODE_HEALTHCHECK_ENDPOINT = getNodeHealthCheckEndpoint(); private static MainHealthCheck healthCheck = new MainHealthCheck(true) .withReporter( new Reporter() .withVersion(Service.BUILD_VERSION) .withName("HERE XYZ Hub") .withBuildDate(Service.BUILD_TIME) .withUpSince(Service.START_TIME) .withEndpoint(getPublicServiceEndpoint()) ) .add(new RedisHealthCheck(Service.configuration.XYZ_HUB_REDIS_HOST, Service.configuration.XYZ_HUB_REDIS_PORT)) .add(new RemoteFunctionHealthAggregator()); static { if (Service.configuration.STORAGE_DB_URL != null) { healthCheck.add( (ExecutableCheck) new JDBCHealthCheck(getStorageDbUri(), Service.configuration.STORAGE_DB_USER, Service.configuration.STORAGE_DB_PASSWORD) .withName("Configuration DB Postgres") .withEssential(true) ); } } public HealthApi(Vertx vertx, Router router) { //The main health check endpoint router.route(HttpMethod.GET, MAIN_HEALTCHECK_ENDPOINT).handler(HealthApi::onHealthStatus); router.route(HttpMethod.GET, "/hub").handler(HealthApi::onHealthStatus); router.route(HttpMethod.GET, "/").handler(HealthApi::onHealthStatus); //TODO: Maybe better replace that one by a redirect to /hub/ //Legacy: router.route(HttpMethod.GET, "/hub/health-status").handler(HealthApi::onHealthStatus); } private static URI getStorageDbUri() { try { return new URI(Service.configuration.STORAGE_DB_URL); } catch (URISyntaxException e) { logger.error("Wrong format of STORAGE_DB_URL: " + Service.configuration.STORAGE_DB_URL, e); return null; } } private static URI getPublicServiceEndpoint() { return URI.create(Service.configuration.XYZ_HUB_PUBLIC_ENDPOINT + MAIN_HEALTCHECK_ENDPOINT); } private static URI getNodeHealthCheckEndpoint() { try { return new URI("http://" + Service.getHostname() + ":" + Service.configuration.HTTP_PORT + MAIN_HEALTCHECK_ENDPOINT); } catch (URISyntaxException e) { logger.error("Wrong format of internal node hostname URI: " + Service.getHostname(), e); return null; } } private static void onHealthStatus(final RoutingContext context) { try { Response r = healthCheck.getResponse(); r.setEndpoint(NODE_HEALTHCHECK_ENDPOINT); r.setNode(Node.OWN_INSTANCE.id); String secretHeaderValue = context.request().getHeader(Config.getHealthCheckHeaderName()); //Always respond with 200 for public HC requests for now int statusCode = r.isPublicRequest(secretHeaderValue) ? OK.code() : r.getStatus().getSuggestedHTTPStatusCode(); String responseString = r.toResponseString(secretHeaderValue); context.response().setStatusCode(statusCode) .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) .end(responseString); } catch (Exception e) { logger.error(Context.getMarker(context), "Error while doing the health-check: ", e); context.response().setStatusCode(OK.code()) .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) .end(new JsonObject().put("status", new JsonObject().put("result", "WARNING")).encode()); } } }