package com.intrbiz.bergamot.health; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; import org.apache.log4j.Logger; import com.intrbiz.Util; import com.intrbiz.bergamot.model.message.health.HealthCheckHeartbeat; import com.intrbiz.bergamot.model.message.health.HealthCheckJoin; import com.intrbiz.bergamot.model.message.health.HealthCheckKill; import com.intrbiz.bergamot.model.message.health.HealthCheckMessage; import com.intrbiz.bergamot.model.message.health.HealthCheckRequestJoin; import com.intrbiz.bergamot.model.message.health.HealthCheckUnjoin; import com.intrbiz.bergamot.queue.HealthCheckQueue; import com.intrbiz.gerald.polyakov.Node; import com.intrbiz.queue.Consumer; import com.intrbiz.queue.Producer; import com.intrbiz.queue.name.NullKey; public final class HealthAgent { private static final HealthAgent US = new HealthAgent(); public static HealthAgent getInstance() { return US; } private UUID instanceId = null; private final UUID runtimeId = UUID.randomUUID(); private String daemonName; private final AtomicLong heartbeatSequence = new AtomicLong(); private volatile boolean inited = false; private long startedAt; private UUID hostId; private String hostName; private String daemonKind; private HealthCheckQueue queue; private Producer<HealthCheckMessage> healthcheckProducer; @SuppressWarnings("unused") private Consumer<HealthCheckMessage, NullKey> healthcheckConsumer; private Timer timer; private HealthAgent() { super(); } public void init(UUID instanceId, String daemonKind, String daemonName, UUID hostId, String hostName) { synchronized (this) { if (! this.inited) { this.inited = true; this.instanceId = instanceId; this.daemonKind = daemonKind; this.daemonName = daemonName; this.hostId = hostId; this.hostName = hostName; this.startedAt = System.currentTimeMillis(); // setup our queues this.setupQueues(); // setup shutdown hook this.setupShutdownHook(); // send join this.joinHealthCheckCluster(); // setup timer this.setupTimer(); } } } public void init(String daemonKind, String daemonName) { Node node = Node.service(daemonName); this.init(computeInstanceId(), daemonKind, daemonName, node.getHostId(), node.getHostName()); } public void init() { Node node = Node.service(); this.init(computeInstanceId(), null, node.getServiceName(), node.getHostId(), node.getHostName()); } public String getDaemonKind() { return this.daemonKind; } public String getDaemonName() { return this.daemonName; } public UUID getInstanceId() { return this.instanceId; } public UUID getRuntimeId() { return this.runtimeId; } public String getHostName() { return this.hostName; } public long getStartedAt() { return this.startedAt; } long nextHeartbeatSequence() { return this.heartbeatSequence.getAndIncrement(); } private void setupQueues() { this.queue = HealthCheckQueue.open(); this.healthcheckProducer = this.queue.publishHealthCheckEvents(); this.healthcheckConsumer = this.queue.consumeHealthCheckControlEvents(this::handleHealthCheckMessage); } private void setupTimer() { this.timer = new Timer(); // heartbeat every 5 seconds this.timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { try { sendHeartbeat(); } catch (Exception e) { // ignore } } }, 5_000L, 5_000L); } private void setupShutdownHook() { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { unjoinHealthCheckCluster(); } }); } private void joinHealthCheckCluster() { // send a join message this.healthcheckProducer.publish(new HealthCheckJoin(this.instanceId, this.runtimeId, this.daemonKind, this.daemonName, this.startedAt, this.hostId, this.hostName)); } private void unjoinHealthCheckCluster() { // send a join message this.healthcheckProducer.publish(new HealthCheckUnjoin(this.instanceId, this.daemonKind, this.daemonName)); } private void sendHeartbeat() { this.healthcheckProducer.publish(new HealthCheckHeartbeat(this.instanceId, System.currentTimeMillis(), this.nextHeartbeatSequence())); } private void handleHealthCheckMessage(Map<String, Object> headers, HealthCheckMessage message) { if (message instanceof HealthCheckRequestJoin) { this.joinHealthCheckCluster(); } else if (message instanceof HealthCheckKill) { this.commitSeppuku((HealthCheckKill) message); } } private void commitSeppuku(HealthCheckKill message) { if (this.instanceId.equals(message.getInstanceId()) && this.runtimeId.equals(message.getRuntimeId())) { String password = Util.coalesceEmpty(System.getProperty("bergamot.health.password"), System.getenv("bergamot.health.password")); if ((! Util.isEmpty(password)) && message.getPassword() != null && message.getPassword().equals(password)) { Logger.getLogger(HealthAgent.class).fatal("Received request to terminate via health check, exiting immediately"); System.err.println("Received request to terminate via health check, exiting immediately"); System.exit(-1); } } } public static final UUID computeInstanceId() { // Attempt to find a configured specific instance id String instanceIdStr = System.getProperty("bergamot.instanceid", System.getenv("BERGAMOT_INSTANCEID")); if (instanceIdStr != null && instanceIdStr.length() > 0) { try { return UUID.fromString(instanceIdStr); } catch (IllegalArgumentException e) { } } Logger.getLogger(HealthAgent.class).warn("Failed to get service instance id, defaulting to a randomly allocated id, please set the environment variable \"BERGAMOT_INSTANCEID\" or the system property \"bergamot.instanceid\""); return UUID.randomUUID(); } }