diff --git a/.gitignore b/.gitignore index 1311f16e..c55e0766 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,8 @@ Thumbs.db */target /build */build - +/bin +*/bin # IntelliJ specific files/directories out .idea diff --git a/karyon-core/build.gradle b/karyon-core/build.gradle index ec9ff71e..71f200d5 100644 --- a/karyon-core/build.gradle +++ b/karyon-core/build.gradle @@ -25,6 +25,7 @@ tasks.withType(Javadoc).each { dependencies { dependencies { compile "com.netflix.rxnetty:rx-netty-contexts:${rxnetty_version}" + compile "com.netflix.governator:governator:1.2.20" } } diff --git a/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheck.java b/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheck.java new file mode 100644 index 00000000..b42d8ebc --- /dev/null +++ b/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheck.java @@ -0,0 +1,108 @@ +package com.netflix.karyon.health; + +/** + * SPI for a component implementation an application status condition. + * Status conditions can indicate one of three scenarios + * 1. Component is not ready (i.e. starting or initializing) + * 2. Component is ready and functioning propertly + * 3. Component has either failed to start or failed during runtime + * + * There can be multiple status conditions registered for a single application. + * The status conditions are ultimately resolved to a single application up/down + * status. + * + * @author elandau + */ +public interface HealthCheck { + /** + * The status can have one of three states + * + * 1. Not ready status = false, error = null + * 2. Ready status = true, error = null + * 3. Error state status = false, error ! =null + * + * @author elandau + * + */ + public static class Status { + private final HealthCheck healthCheck; + private final Throwable error; + private final boolean status; + + private Status(HealthCheck healthCheck, boolean status, Throwable error) { + this.status = status; + this.error = error; + this.healthCheck = healthCheck; + } + + /** + * Component is ready to be used and functioning properly + * @return + */ + public boolean isReady() { + return status && error == null; + } + + /** + * Component is either not ready or failed + */ + public boolean isNotReady() { + return !isReady(); + } + + /** + * There was an error starting or running a component + */ + public boolean hasError() { + return error != null; + } + + public Throwable getError() { + return error; + } + + /** + * @return Name of component(s) being checked by this StatusCheck + */ + public String getName() { + return healthCheck.getName(); + } + + public static Status error(HealthCheck healthCheck, Throwable error) { + return new Status(healthCheck, false, error); + } + + public static Status ready(HealthCheck healthCheck) { + return new Status(healthCheck, true, null); + } + + public static Status notReady(HealthCheck healthCheck) { + return new Status(healthCheck, false, null); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Status [") + .append("healthCheck=").append(healthCheck.getName()) + .append(", status=") .append(status); + + if (error != null) { + sb.append(", error=" + error.getMessage()); + } + sb.append("]"); + return sb.toString(); + } + } + + /** + * Run the status check and return the current status + * @return + */ + public abstract Status check(); + + /** + * @return Get name for health check + */ + public abstract String getName(); +} diff --git a/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheckHandler.java b/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheckHandler.java index e52449a3..96e5233f 100644 --- a/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheckHandler.java +++ b/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheckHandler.java @@ -16,6 +16,7 @@ package com.netflix.karyon.health; + /** * This is an extension to the callback handler * in eureka to provide a diff --git a/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheckInvoker.java b/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheckInvoker.java new file mode 100644 index 00000000..ce4d5f74 --- /dev/null +++ b/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheckInvoker.java @@ -0,0 +1,17 @@ +package com.netflix.karyon.health; + +import java.util.Collection; +import java.util.List; + +import com.google.inject.ImplementedBy; + +/** + * Strategy for invoking the actual healthcheck operations + * + * @author elandau + * + */ +@ImplementedBy(InlineHealthCheckInvoker.class) +public interface HealthCheckInvoker { + public List invoke(Collection healthChecks); +} diff --git a/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheckRegistry.java b/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheckRegistry.java new file mode 100644 index 00000000..ec3cae1e --- /dev/null +++ b/karyon-core/src/main/java/com/netflix/karyon/health/HealthCheckRegistry.java @@ -0,0 +1,23 @@ +package com.netflix.karyon.health; + +import java.util.List; + +/** + * Registry of all status checkers. The actual health check is performed + * by an implementation of {@link HealthCheckInvoker} + * + * @author elandau + */ +public interface HealthCheckRegistry { + /** + * @return Return list of all registered HealthCheck conditions + */ + List getHealthChecks(); + + /** + * Add a HealthCheck to the registry + * + * @param handler + */ + void registerHealthCheck(HealthCheck handler); +} diff --git a/karyon-core/src/main/java/com/netflix/karyon/health/InlineHealthCheckInvoker.java b/karyon-core/src/main/java/com/netflix/karyon/health/InlineHealthCheckInvoker.java new file mode 100644 index 00000000..a09cbc27 --- /dev/null +++ b/karyon-core/src/main/java/com/netflix/karyon/health/InlineHealthCheckInvoker.java @@ -0,0 +1,26 @@ +package com.netflix.karyon.health; + +import java.util.Collection; +import java.util.List; + +import javax.inject.Singleton; + +import com.google.common.collect.Lists; +import com.netflix.karyon.health.HealthCheck.Status; + +/** + * Invoke all health checks in the context of the calling thread + * @author elandau + * + */ +@Singleton +public class InlineHealthCheckInvoker implements HealthCheckInvoker { + @Override + public List invoke(Collection healthChecks) { + List response = Lists.newArrayList(); + for (HealthCheck hc : healthChecks) { + response.add(hc.check()); + } + return response; + } +} diff --git a/karyon-core/src/main/java/com/netflix/karyon/health/ManualHealthCheck.java b/karyon-core/src/main/java/com/netflix/karyon/health/ManualHealthCheck.java new file mode 100644 index 00000000..46542d48 --- /dev/null +++ b/karyon-core/src/main/java/com/netflix/karyon/health/ManualHealthCheck.java @@ -0,0 +1,43 @@ +package com.netflix.karyon.health; + +/** + * Implementation of HealthCheck that may be set manually. + * + * @author elandau + */ +public class ManualHealthCheck implements HealthCheck { + private volatile Status status; + private final String name; + + public ManualHealthCheck() { + this("manual"); + } + + public ManualHealthCheck(String name) { + this.name = name; + this.status = Status.error(this, null); + } + + public void unhealthy(Throwable error) { + this.status = Status.error(this, error); + } + + public void healthy() { + this.status = Status.ready(this); + } + + @Override + public String getName() { + return name; + } + + @Override + public Status check() { + return status; + } + + @Override + public String toString() { + return "ManualHealthCheck [status=" + status + ", name=" + name + "]"; + } +} diff --git a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/ApplicationInfoManagerHealthCheck.java b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/ApplicationInfoManagerHealthCheck.java new file mode 100644 index 00000000..73d64130 --- /dev/null +++ b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/ApplicationInfoManagerHealthCheck.java @@ -0,0 +1,59 @@ +package com.netflix.karyon.eureka; + +import javax.inject.Singleton; + +import com.google.inject.Inject; +import com.netflix.appinfo.ApplicationInfoManager; +import com.netflix.appinfo.InstanceInfo.InstanceStatus; +import com.netflix.karyon.health.HealthCheck; + +/** + * StatusCheck based on the ApplicationInfoManager tracked status. + * + * @author elandau + */ +@Singleton +public class ApplicationInfoManagerHealthCheck implements HealthCheck { + ApplicationInfoManager manager; + + @Deprecated + public ApplicationInfoManagerHealthCheck() { + manager = ApplicationInfoManager.getInstance(); + } + + @Inject + public ApplicationInfoManagerHealthCheck(ApplicationInfoManager manager) { + this.manager = manager; + } + + @Override + public Status check() { + InstanceStatus status = manager.getInstanceStatus(); + if (status != null) { + switch (status) { + case UP: + return Status.ready(this); + case STARTING: + return Status.error(this, null); + case DOWN: + return Status.error(this, new Exception("Application is DOWN")); + default: + return Status.error(this, new Exception("Invalid state : " + status)); + } + } + else { + return Status.error(this, new Exception("Invalid status ")); + } + } + + @Override + public String getName() { + return "ApplicationInfoManager"; + } + + @Override + public String toString() { + return "ApplicationInfoManagerHealthCheck []"; + } + +} diff --git a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/DefaultEurekaKaryonStatusBridge.java b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/DefaultEurekaKaryonStatusBridge.java index b7794a48..17944911 100644 --- a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/DefaultEurekaKaryonStatusBridge.java +++ b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/DefaultEurekaKaryonStatusBridge.java @@ -5,6 +5,7 @@ /** * @author Nitesh Kant */ +@Deprecated public class DefaultEurekaKaryonStatusBridge implements EurekaKaryonStatusBridge { @Override diff --git a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/DefaultHealthCheckRegistry.java b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/DefaultHealthCheckRegistry.java new file mode 100644 index 00000000..de8566b4 --- /dev/null +++ b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/DefaultHealthCheckRegistry.java @@ -0,0 +1,82 @@ +package com.netflix.karyon.eureka; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.inject.Provider; +import javax.inject.Singleton; + +import com.google.inject.Inject; +import com.google.inject.util.Providers; +import com.netflix.karyon.health.HealthCheckHandler; +import com.netflix.karyon.health.HealthCheck; +import com.netflix.karyon.health.HealthCheckRegistry; + +/** + * Simple health check registry that supports the following HealthChecks + * 1. Bridge for ApplicationInfoManager's manual HealthCheck status + * 2. Bridge for HealthCheckHandler + * 3. Set creating using guice's multibinding + * + * Note that all healthchecks are injected lazily using Providers to ensure there is no + * circular dependency for components that depend on DiscoveryClient. + * + * @author elandau + */ +@Singleton +public class DefaultHealthCheckRegistry implements HealthCheckRegistry { + private final CopyOnWriteArrayList> healthChecks = new CopyOnWriteArrayList>(); + + public static class OptionalArgs { + @Inject(optional=true) + Provider handler; + + @Inject(optional=true) + Set> healthChecks; + } + + @Inject + DefaultHealthCheckRegistry(Provider manager, OptionalArgs args) { + this(args.healthChecks, manager, args.handler); + } + + public DefaultHealthCheckRegistry( + final Set> healthChecks, + final Provider manager, + final Provider handler) { + + if (manager != null) { + this.healthChecks.add(manager); + } + + if (handler != null) { + this.healthChecks.add(Providers.of(new HealthCheckHandlerToHealthCheckAdapter(handler, "legacy"))); + } + + if (healthChecks != null) { + this.healthChecks.addAll(healthChecks); + } + } + + /** + * Return a list of ALL registered handlers + */ + @Override + public List getHealthChecks() { + List statuses = new ArrayList(); + for (Provider provider : healthChecks) { + HealthCheck hc = provider.get(); + if (hc != null) { + statuses.add(provider.get()); + } + } + return statuses; + } + + @Override + public void registerHealthCheck(HealthCheck handler) { + this.healthChecks.add(Providers.of(handler)); + } +} diff --git a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/EurekaHealthCheckHandler.java b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/EurekaHealthCheckHandler.java index 3bf4a163..0fb56756 100644 --- a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/EurekaHealthCheckHandler.java +++ b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/EurekaHealthCheckHandler.java @@ -16,31 +16,30 @@ package com.netflix.karyon.eureka; -import com.netflix.appinfo.InstanceInfo; -import com.netflix.karyon.health.HealthCheckHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import javax.inject.Inject; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.karyon.health.HealthCheckInvoker; +import com.netflix.karyon.health.HealthCheckRegistry; + /** * @author Nitesh Kant */ public class EurekaHealthCheckHandler implements com.netflix.appinfo.HealthCheckHandler { - private final HealthCheckHandler healthCheckHandler; - private final EurekaKaryonStatusBridge eurekaKaryonStatusBridge; + private final HealthCheckRegistry registry; + private HealthCheckInvoker invoker; + private EurekaHealthCheckResolver resolver; @Inject - public EurekaHealthCheckHandler(HealthCheckHandler healthCheckHandler, - EurekaKaryonStatusBridge eurekaKaryonStatusBridge) { - this.healthCheckHandler = healthCheckHandler; - this.eurekaKaryonStatusBridge = eurekaKaryonStatusBridge; + public EurekaHealthCheckHandler(HealthCheckRegistry registry, HealthCheckInvoker invoker, EurekaHealthCheckResolver resolver) { + this.registry = registry; + this.invoker = invoker; + this.resolver = resolver; } @Override public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus currentStatus) { - int healthStatus = healthCheckHandler.getStatus(); - return eurekaKaryonStatusBridge.interpretKaryonStatus(healthStatus); + return resolver.resolve(invoker.invoke(registry.getHealthChecks())); } } diff --git a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/EurekaHealthCheckResolver.java b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/EurekaHealthCheckResolver.java new file mode 100644 index 00000000..37ba09cd --- /dev/null +++ b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/EurekaHealthCheckResolver.java @@ -0,0 +1,17 @@ +package com.netflix.karyon.eureka; + +import java.util.Collection; + +import com.google.inject.ImplementedBy; +import com.netflix.appinfo.InstanceInfo.InstanceStatus; +import com.netflix.karyon.health.HealthCheck; + +/** + * SPI for strategy to resolve a set of {@link HealthCheck.Status}'s into a single + * {@link HealthCheck.Status} + * @author elandau + */ +@ImplementedBy(WorstStatusEurekaHealthCheckResolver.class) +public interface EurekaHealthCheckResolver { + InstanceStatus resolve(Collection statuses); +} diff --git a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/EurekaKaryonStatusBridge.java b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/EurekaKaryonStatusBridge.java index f6fda2a6..836ad669 100644 --- a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/EurekaKaryonStatusBridge.java +++ b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/EurekaKaryonStatusBridge.java @@ -6,6 +6,7 @@ /** * @author Nitesh Kant */ +@Deprecated @ImplementedBy(DefaultEurekaKaryonStatusBridge.class) public interface EurekaKaryonStatusBridge { diff --git a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/HealthCheckCallbackToHealthCheckAdapter.java b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/HealthCheckCallbackToHealthCheckAdapter.java new file mode 100644 index 00000000..aad11538 --- /dev/null +++ b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/HealthCheckCallbackToHealthCheckAdapter.java @@ -0,0 +1,37 @@ +package com.netflix.karyon.eureka; + +import com.netflix.appinfo.HealthCheckCallback; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.karyon.health.HealthCheck; + +public class HealthCheckCallbackToHealthCheckAdapter implements HealthCheck { + private final HealthCheckCallback callback; + private final InstanceInfo instanceInfo; + private final String name; + + public HealthCheckCallbackToHealthCheckAdapter(HealthCheckCallback callback, InstanceInfo instanceInfo) { + this.callback = callback; + this.instanceInfo = instanceInfo; + this.name = callback.getClass().getName(); + } + + @Override + public Status check() { + try { + if (callback.isHealthy()) { + return Status.ready(this); + } + else { + return Status.notReady(this); + } + } + catch (Exception e) { + return Status.error(this, e); + } + } + + @Override + public String getName() { + return name; + } +} diff --git a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/HealthCheckHandlerToHealthCheckAdapter.java b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/HealthCheckHandlerToHealthCheckAdapter.java new file mode 100644 index 00000000..88329122 --- /dev/null +++ b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/HealthCheckHandlerToHealthCheckAdapter.java @@ -0,0 +1,52 @@ +package com.netflix.karyon.eureka; + +import javax.inject.Provider; + +import com.google.inject.util.Providers; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.karyon.health.HealthCheckHandler; +import com.netflix.karyon.health.HealthCheck; + +/** + * Adapter to convert a HealthCheckHandler into a HealthCheck.Status + * + * @author elandau + */ +public class HealthCheckHandlerToHealthCheckAdapter implements HealthCheck { + private final Provider handler; + private final String name; + + public HealthCheckHandlerToHealthCheckAdapter(Provider handler, String name) { + this.handler = handler; + this.name = name; + } + + public HealthCheckHandlerToHealthCheckAdapter(HealthCheckHandler handler, InstanceInfo instanceInfo) { + this.handler = Providers.of(handler); + this.name = handler.getClass().getName(); + } + + @Override + public Status check() { + try { + int status = handler.get().getStatus(); + if (status == 204) { + return Status.notReady(this); + } + else if (status >= 200 || status <= 300) { + return Status.ready(this); + } + else { + return Status.error(this, new Exception("Bad status " + status)); + } + } + catch (Exception e) { + return Status.error(this, e); + } + } + + @Override + public String getName() { + return name; + } +} diff --git a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/KaryonEurekaModule.java b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/KaryonEurekaModule.java index ce172353..ff372a43 100644 --- a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/KaryonEurekaModule.java +++ b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/KaryonEurekaModule.java @@ -11,6 +11,7 @@ import com.netflix.discovery.providers.DefaultEurekaClientConfigProvider; import com.netflix.governator.guice.LifecycleInjectorBuilder; import com.netflix.governator.guice.LifecycleInjectorBuilderSuite; +import com.netflix.karyon.health.HealthCheckRegistry; /** * @author Nitesh Kant @@ -22,6 +23,7 @@ protected void configure() { bind(com.netflix.appinfo.HealthCheckHandler.class).to(EurekaHealthCheckHandler.class); bind(ApplicationInfoManager.class).asEagerSingleton(); bind(DiscoveryClient.class).asEagerSingleton(); + bind(HealthCheckRegistry.class).to(DefaultHealthCheckRegistry.class); configureEureka(); } diff --git a/karyon-eureka/src/main/java/com/netflix/karyon/eureka/WorstStatusEurekaHealthCheckResolver.java b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/WorstStatusEurekaHealthCheckResolver.java new file mode 100644 index 00000000..47a79dcb --- /dev/null +++ b/karyon-eureka/src/main/java/com/netflix/karyon/eureka/WorstStatusEurekaHealthCheckResolver.java @@ -0,0 +1,35 @@ +package com.netflix.karyon.eureka; + +import java.util.Collection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.appinfo.InstanceInfo.InstanceStatus; +import com.netflix.karyon.health.HealthCheck; + +/** + * Specialized HealthCheckResolver that will return the first occurance of DOWN + * or STARTING (in that order) or UP if all status checks are ready. + * @author elandau + * + */ +public class WorstStatusEurekaHealthCheckResolver implements EurekaHealthCheckResolver { + private static final Logger LOG = LoggerFactory.getLogger(WorstStatusEurekaHealthCheckResolver.class); + + @Override + public InstanceStatus resolve(Collection statuses) { + if (statuses != null) { + for (HealthCheck.Status status : statuses) { + LOG.info("Status of '{}' : {} ({})", status.getName(), status.isReady(), status.getError()); + if (status.hasError()) { + return InstanceStatus.DOWN; + } + if (!status.isReady()) { + return InstanceStatus.STARTING; + } + } + } + return InstanceStatus.UP; + } +} diff --git a/karyon-eureka/src/test/java/com/netflix/karyon/eureka/HealthCheckRegistryTest.java b/karyon-eureka/src/test/java/com/netflix/karyon/eureka/HealthCheckRegistryTest.java new file mode 100644 index 00000000..18454702 --- /dev/null +++ b/karyon-eureka/src/test/java/com/netflix/karyon/eureka/HealthCheckRegistryTest.java @@ -0,0 +1,152 @@ +package com.netflix.karyon.eureka; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.inject.Singleton; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.google.inject.AbstractModule; +import com.google.inject.Injector; +import com.netflix.appinfo.ApplicationInfoManager; +import com.netflix.appinfo.DataCenterInfo; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.appinfo.InstanceInfo.InstanceStatus; +import com.netflix.config.ConfigurationManager; +import com.netflix.discovery.MockRemoteEurekaServer; +import com.netflix.governator.guice.LifecycleInjector; +import com.netflix.governator.guice.LifecycleInjectorMode; +import com.netflix.karyon.health.HealthCheckHandler; +import com.netflix.karyon.health.HealthCheckInvoker; +import com.netflix.karyon.health.HealthCheckRegistry; + +public class HealthCheckRegistryTest { + public static final String REMOTE_REGION = "myregion"; + public static final String REMOTE_ZONE = "myzone"; + + @Singleton + public static class TestModule extends AbstractModule { + @Override + protected void configure() { + ConfigurationManager.getConfigInstance().setProperty("eureka.shouldFetchRegistry", "true"); + ConfigurationManager.getConfigInstance().setProperty("eureka.responseCacheAutoExpirationInSeconds", "10"); + ConfigurationManager.getConfigInstance().setProperty("eureka.client.refresh.interval", "60"); + ConfigurationManager.getConfigInstance().setProperty("eureka.registration.enabled", "false"); + ConfigurationManager.getConfigInstance().setProperty("eureka.validateInstanceId", "false"); + ConfigurationManager.getConfigInstance().setProperty("eureka.fetchRemoteRegionsRegistry", REMOTE_REGION); + ConfigurationManager.getConfigInstance().setProperty("eureka.myregion.availabilityZones", REMOTE_ZONE); + ConfigurationManager.getConfigInstance().setProperty("eureka.serviceUrl.default", + "http://localhost:" + 7777 + + MockRemoteEurekaServer.EUREKA_API_BASE_PATH); + + DataCenterInfo myDCI = new DataCenterInfo() { + public DataCenterInfo.Name getName() { return DataCenterInfo.Name.MyOwn; } + }; + + bind(HealthCheckRegistry.class).to(DefaultHealthCheckRegistry.class); + bind(com.netflix.appinfo.HealthCheckHandler.class).to(EurekaHealthCheckHandler.class); + bind(InstanceInfo.class).toInstance( + InstanceInfo.Builder.newBuilder() + .setAppName("test") + .setStatus(InstanceStatus.STARTING) + .setDataCenterInfo(myDCI).build()); + } + } + + @Test + public void defaultShouldBeStarting() { + Injector injector = LifecycleInjector.builder() + .withModuleClasses(TestModule.class) + .build() + .createInjector(); + HealthCheckRegistry registry = injector.getInstance(HealthCheckRegistry.class); + com.netflix.appinfo.HealthCheckHandler handler = injector.getInstance(com.netflix.appinfo.HealthCheckHandler.class); + + Assert.assertEquals(1, registry.getHealthChecks().size()); + + Assert.assertEquals(InstanceStatus.STARTING, handler.getStatus(null)); + + ApplicationInfoManager.getInstance().setInstanceStatus(InstanceStatus.UP); + Assert.assertEquals(InstanceStatus.UP, handler.getStatus(null)); + } + + @Test + public void defaultShouldBeStartingWithDefaultHealthCheck() { + final AtomicInteger status = new AtomicInteger(204); + + Injector injector = LifecycleInjector.builder() + .withMode(LifecycleInjectorMode.SIMULATED_CHILD_INJECTORS) + .withModuleClasses(TestModule.class) + .withAdditionalModules(new AbstractModule() { + @Override + protected void configure() { + bind(HealthCheckHandler.class).toInstance(new HealthCheckHandler() { + @Override + public int getStatus() { + return status.get(); + } + }); + } + }) + .build() + .createInjector(); + HealthCheckHandler hcHandler = injector.getInstance(HealthCheckHandler.class); + HealthCheckRegistry registry = injector.getInstance(HealthCheckRegistry.class); + HealthCheckInvoker invoker = injector.getInstance(HealthCheckInvoker.class); + com.netflix.appinfo.HealthCheckHandler handler = injector.getInstance(com.netflix.appinfo.HealthCheckHandler.class); + + Assert.assertNotNull(hcHandler); + + Assert.assertEquals(2, registry.getHealthChecks().size()); + + Assert.assertEquals(InstanceStatus.STARTING, handler.getStatus(null)); + + ApplicationInfoManager.getInstance().setInstanceStatus(InstanceStatus.UP); + status.set(200); + + Assert.assertEquals(InstanceStatus.UP, handler.getStatus(null)); + + status.set(500); + + Assert.assertEquals(InstanceStatus.DOWN, handler.getStatus(null)); + + System.out.println(registry.getHealthChecks()); + System.out.println(invoker.invoke(registry.getHealthChecks())); + } + + @Test + public void reportExceptionForFailingHealthCheck() { + Injector injector = LifecycleInjector.builder() + .withMode(LifecycleInjectorMode.SIMULATED_CHILD_INJECTORS) + .withModuleClasses(TestModule.class) + .withAdditionalModules(new AbstractModule() { + @Override + protected void configure() { + bind(HealthCheckHandler.class).toInstance(new HealthCheckHandler() { + @Override + public int getStatus() { + throw new RuntimeException("Failed"); + } + }); + } + }) + .build() + .createInjector(); + HealthCheckHandler hcHandler = injector.getInstance(HealthCheckHandler.class); + HealthCheckRegistry registry = injector.getInstance(HealthCheckRegistry.class); + HealthCheckInvoker invoker = injector.getInstance(HealthCheckInvoker.class); + com.netflix.appinfo.HealthCheckHandler handler = injector.getInstance(com.netflix.appinfo.HealthCheckHandler.class); + + Assert.assertNotNull(hcHandler); + + Assert.assertEquals(2, registry.getHealthChecks().size()); + ApplicationInfoManager.getInstance().setInstanceStatus(InstanceStatus.UP); + + Assert.assertEquals(InstanceStatus.DOWN, handler.getStatus(null)); + + System.out.println(registry.getHealthChecks()); + System.out.println(invoker.invoke(registry.getHealthChecks())); + } +}