-
Notifications
You must be signed in to change notification settings - Fork 152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for multiple HealthChecks that resolve to a single health check status #117
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,7 +46,8 @@ Thumbs.db | |
*/target | ||
/build | ||
*/build | ||
|
||
/bin | ||
*/bin | ||
# IntelliJ specific files/directories | ||
out | ||
.idea | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we are moving away from single boolean, why not introduce a more complete service status model with { Starting, PartiallyReady, Ready, Down, Error } states? There is different semantic associated with each:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With this solution I'm trying to minimize the different states being tracked and instead opt to have multiple conditions. The above states can be modeled using the following conditions
Each of these conditions would have a boolean ok/bad as well as an optional error. So a failure to start would have 'starting' set to false with error. |
||
* @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(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<HealthCheck.Status> invoke(Collection<HealthCheck> healthChecks); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<HealthCheck> getHealthChecks(); | ||
|
||
/** | ||
* Add a HealthCheck to the registry | ||
* | ||
* @param handler | ||
*/ | ||
void registerHealthCheck(HealthCheck handler); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Status> invoke(Collection<HealthCheck> healthChecks) { | ||
List<Status> response = Lists.newArrayList(); | ||
for (HealthCheck hc : healthChecks) { | ||
response.add(hc.check()); | ||
} | ||
return response; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 + "]"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <null>")); | ||
} | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return "ApplicationInfoManager"; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "ApplicationInfoManagerHealthCheck []"; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<HealthCheck> 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<Provider<? extends HealthCheck>> healthChecks = new CopyOnWriteArrayList<Provider<? extends HealthCheck>>(); | ||
|
||
public static class OptionalArgs { | ||
@Inject(optional=true) | ||
Provider<HealthCheckHandler> handler; | ||
|
||
@Inject(optional=true) | ||
Set<Provider<HealthCheck>> healthChecks; | ||
} | ||
|
||
@Inject | ||
DefaultHealthCheckRegistry(Provider<ApplicationInfoManagerHealthCheck> manager, OptionalArgs args) { | ||
this(args.healthChecks, manager, args.handler); | ||
} | ||
|
||
public DefaultHealthCheckRegistry( | ||
final Set<Provider<HealthCheck>> healthChecks, | ||
final Provider<ApplicationInfoManagerHealthCheck> manager, | ||
final Provider<HealthCheckHandler> 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<HealthCheck> getHealthChecks() { | ||
List<HealthCheck> statuses = new ArrayList<HealthCheck>(); | ||
for (Provider<? extends HealthCheck> 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)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
governator dependency should not be added in core. karyon-governator is the module for governator integration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. This was done so ImplementedBy could be used. At minimum this should be changed to a dependency on Guice instead of Governator. We could remove ImplementedBy but that would require the user to install a guice module.