All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.wisetime.connector.health.HealthCheck Maven / Gradle / Ivy

There is a newer version: 2.2.6
Show newest version
/*
 * Copyright (c) 2018 Practice Insight Pty Ltd. All Rights Reserved.
 */

package io.wisetime.connector.health;

import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

import io.wisetime.connector.IntegrateApplication;
import io.wisetime.connector.config.ConnectorConfigKey;
import io.wisetime.connector.config.RuntimeConfig;

/**
 * @author [email protected]
 */
public class HealthCheck extends TimerTask {

  private static final Logger log = LoggerFactory.getLogger(HealthCheck.class);

  // visible for testing
  static final int MAX_SUCCESSIVE_FAILURES = 3;
  static final int MAX_MINS_SINCE_SUCCESS_DEFAULT = 60;

  private final int port;
  private final Executor executor = Executor.newInstance();
  private final Supplier lastRunSuccess;
  private final Supplier connectorHealthCheck;
  private final AtomicInteger failureCount;
  private final int maxMinsSinceSuccess;

  // modifiable for testing
  private int latencyTolerance;
  private Runnable shutdownFunction;

  /**
   * @param port                 The port that server is running on in this VM.
   * @param lastRunSuccess       Provides the last time the tag upsert method was run.
   * @param connectorHealthCheck Optionally, the connector implementation can provide a health check via this function.
   */
  public HealthCheck(int port, Supplier lastRunSuccess, Supplier connectorHealthCheck) {
    this.port = port;
    this.lastRunSuccess = lastRunSuccess;
    this.connectorHealthCheck = connectorHealthCheck;
    this.failureCount = new AtomicInteger(0);
    this.shutdownFunction = () -> {
      try {
        // allow time for logs to reach AWS before killing VM
        Thread.sleep(5000);
      } catch (InterruptedException e) {
        log.info(e.getMessage());
      }
      System.exit(-1);
    };
    this.latencyTolerance = 2000;
    this.maxMinsSinceSuccess = RuntimeConfig
        .getInt(ConnectorConfigKey.HEALTH_MAX_MINS_SINCE_SUCCESS)
        .orElse(MAX_MINS_SINCE_SUCCESS_DEFAULT);
  }

  @Override
  public void run() {
    boolean healthy = checkServerHealth();
    if (healthy) {
      failureCount.set(0);
      log.info("health-check-success");
    } else {
      // increment fail count, and if more than 3 successive errors, call shutdown function
      if (failureCount.incrementAndGet() >= MAX_SUCCESSIVE_FAILURES) {
        log.error("After {} successive errors, VM is assumed unhealthy, exiting", MAX_SUCCESSIVE_FAILURES);
        shutdownFunction.run();
      } else {
        log.warn("Number of successive health failures={}", failureCount.get());
      }
    }
  }

  private boolean checkServerHealth() {
    try {
      final DateTime lastSuccessResult = lastRunSuccess.get();
      if (DateTime.now().minusMinutes(maxMinsSinceSuccess).isAfter(lastSuccessResult)) {
        log.info(
            "unhealthy state where lastRunSuccess ({}) is not within the last {}mins (maxMinutesSinceSuccess)",
            lastSuccessResult,
            maxMinsSinceSuccess
        );
        return false;
      }

      if (Boolean.FALSE.equals(connectorHealthCheck.get())) {
        log.info("connectorHealthCheck returned false");
        return false;
      }

      return checkEndpointHealth();
    } catch (Throwable t) {
      log.error("Exception occurred checking health, returning unhealthy; msg='{}'", t.getMessage(), t);
      return false;
    }
  }

  private boolean checkEndpointHealth() throws IOException {
    if (port == 0) {
      // running on ephemeral port, skip http check
      return true;
    }

    log.debug("calling local endpoint over http to check server is responding");
    // call health endpoint to check responding & status of 200 re response
    String result = executor.execute(
        Request.Get(String.format("http://localhost:%d/ping", port))
            .connectTimeout(latencyTolerance)
            .socketTimeout(latencyTolerance))
        .returnContent().asString();

    return IntegrateApplication.PING_RESPONSE.equals(result);
  }

  // Visible for testing
  HealthCheck setShutdownFunction(Runnable shutdownFunction) {
    this.shutdownFunction = shutdownFunction;
    return this;
  }

  // Visible for testing
  HealthCheck setLowLatencyTolerance() {
    this.latencyTolerance = 100;
    return this;
  }

  // visible for testing
  int getFailureCount() {
    return failureCount.get();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy