Skip to content
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

FISH-660 Upgrade MicroProfile Health Implementation to 3.0-RC5 #4986

Merged
merged 5 commits into from Nov 23, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -39,18 +39,16 @@
*/
package fish.payara.microprofile.healthcheck;

import fish.payara.microprofile.healthcheck.config.MetricsHealthCheckConfiguration;
import fish.payara.microprofile.healthcheck.checks.PayaraHealthCheck;
import fish.payara.nucleus.healthcheck.configuration.Checker;
import fish.payara.nucleus.healthcheck.events.PayaraHealthCheckServiceEvents;
import fish.payara.monitoring.collect.MonitoringData;
import fish.payara.monitoring.collect.MonitoringDataCollector;
import fish.payara.monitoring.collect.MonitoringDataSource;
import fish.payara.monitoring.collect.MonitoringWatchCollector;
import fish.payara.monitoring.collect.MonitoringWatchSource;
import static fish.payara.microprofile.healthcheck.HealthCheckType.LIVENESS;
import static fish.payara.microprofile.healthcheck.HealthCheckType.READINESS;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonMap;
import static java.util.logging.Level.WARNING;
import static java.util.stream.Collectors.joining;

import java.beans.PropertyChangeEvent;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
Expand All @@ -70,12 +68,14 @@
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.json.JsonWriter;
import javax.json.JsonWriterFactory;
import javax.json.stream.JsonGenerator;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.HealthCheckResponse.State;
import org.eclipse.microprofile.health.HealthCheckResponse.Status;
import org.glassfish.api.StartupRunLevel;
import org.glassfish.api.event.EventListener;
import org.glassfish.api.event.Events;
Expand All @@ -88,17 +88,15 @@
import org.jvnet.hk2.config.UnprocessedChangeEvent;
import org.jvnet.hk2.config.UnprocessedChangeEvents;

import static fish.payara.microprofile.healthcheck.HealthCheckType.HEALTH;
import static fish.payara.microprofile.healthcheck.HealthCheckType.LIVENESS;
import static fish.payara.microprofile.healthcheck.HealthCheckType.READINESS;
import java.io.StringWriter;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonMap;
import static java.util.logging.Level.WARNING;
import static java.util.stream.Collectors.joining;
import javax.json.JsonWriter;
import javax.json.JsonWriterFactory;
import javax.json.stream.JsonGenerator;
import fish.payara.microprofile.healthcheck.checks.PayaraHealthCheck;
import fish.payara.microprofile.healthcheck.config.MicroprofileHealthCheckConfiguration;
import fish.payara.monitoring.collect.MonitoringData;
import fish.payara.monitoring.collect.MonitoringDataCollector;
import fish.payara.monitoring.collect.MonitoringDataSource;
import fish.payara.monitoring.collect.MonitoringWatchCollector;
import fish.payara.monitoring.collect.MonitoringWatchSource;
import fish.payara.nucleus.healthcheck.configuration.Checker;
import fish.payara.nucleus.healthcheck.events.PayaraHealthCheckServiceEvents;

/**
* Service that handles the registration, execution, and response of MicroProfile HealthChecks.
Expand All @@ -116,16 +114,10 @@ public class HealthCheckService implements EventListener, ConfigListener, Monito
private ApplicationRegistry applicationRegistry;

@Inject
private MetricsHealthCheckConfiguration configuration;

@Inject
private ConfigProviderResolver microConfigResolver;

private boolean backwardCompEnabled;
private MicroprofileHealthCheckConfiguration configuration;

private static final Logger LOG = Logger.getLogger(HealthCheckService.class.getName());

private final Map<String, Set<HealthCheck>> health = new ConcurrentHashMap<>();
private final Map<String, Set<HealthCheck>> readiness = new ConcurrentHashMap<>();
private final Map<String, Set<HealthCheck>> liveness = new ConcurrentHashMap<>();

Expand All @@ -134,45 +126,37 @@ public class HealthCheckService implements EventListener, ConfigListener, Monito

private final AtomicReference<Map<String, Set<String>>> checksCollected = new AtomicReference<>();

private static final String BACKWARD_COMP_ENABLED_PROPERTY = "MP_HEALTH_BACKWARD_COMPATIBILITY_ENABLED";

@PostConstruct
public void postConstruct() {
if (events == null) {
events = Globals.getDefaultBaseServiceLocator().getService(Events.class);
}
events.register(this);
this.backwardCompEnabled = microConfigResolver.getConfig()
.getOptionalValue(BACKWARD_COMP_ENABLED_PROPERTY, Boolean.class)
.orElse(false);
}

@Override
@MonitoringData(ns = "health", intervalSeconds = 12)
public void collect(MonitoringDataCollector collector) {
Map<String, Set<String>> collected = new HashMap<>();
Map<String, List<HealthCheckResponse>> healthResponsesByAppName = collectChecks(collector, health, collected);
Map<String, List<HealthCheckResponse>> readinessResponsesByAppName = collectChecks(collector, readiness, collected);
Map<String, List<HealthCheckResponse>> livenessResponsesByAppName = collectChecks(collector, liveness, collected);
checksCollected.set(collected);
if (!collected.isEmpty()) {
List<HealthCheckResponse> overall = new ArrayList<>();
overall.addAll(collectJointType(collector, "Health", healthResponsesByAppName));
overall.addAll(collectJointType(collector, "Readiness", readinessResponsesByAppName));
overall.addAll(collectJointType(collector, "Liveness", livenessResponsesByAppName));
collectUpDown(collector, computeJointState("Overall", overall));
}
for (String appName : collected.keySet()) {
List<HealthCheckResponse> overallByApp = new ArrayList<>();
overallByApp.addAll(healthResponsesByAppName.getOrDefault(appName, emptyList()));
overallByApp.addAll(readinessResponsesByAppName.getOrDefault(appName, emptyList()));
overallByApp.addAll(livenessResponsesByAppName.getOrDefault(appName, emptyList()));
collectUpDown(collector.group(appName), computeJointState("Overall", overallByApp));
}
}

private static void collectUpDown(MonitoringDataCollector collector, HealthCheckResponse response) {
collector.collect(response.getName(), response.getState() == State.UP ? 1 : 0);
collector.collect(response.getName(), response.getStatus() == Status.UP ? 1 : 0);
}

private static List<HealthCheckResponse> collectJointType(MonitoringDataCollector collector, String type,
Expand All @@ -196,16 +180,14 @@ public void collect(MonitoringWatchCollector collector) {
for (String metric : e.getValue()) {
addWatch(collector, appName, metric);
}
addWatch(collector, appName, "Health");
addWatch(collector, appName, "Readiness");
addWatch(collector, appName, "Liveness");
addWatch(collector, appName, "Overall");
addWatch(collector, appName, "Health");
}
if (!collected.isEmpty()) {
addWatch(collector, null, "Health");
addWatch(collector, null, "Readiness");
addWatch(collector, null, "Liveness");
addWatch(collector, null, "Overall");
addWatch(collector, null, "Health");
}
}
}
Expand All @@ -221,7 +203,7 @@ private static void addWatch(MonitoringWatchCollector collector, String appName,

private Map<String, List<HealthCheckResponse>> collectChecks(MonitoringDataCollector collector,
Map<String, Set<HealthCheck>> checks, Map<String, Set<String>> collected) {
Map<String, List<HealthCheckResponse>> stateByApp = new HashMap<>();
Map<String, List<HealthCheckResponse>> statusByApp = new HashMap<>();
for (Entry<String, Set<HealthCheck>> entry : checks.entrySet()) {
String appName = entry.getKey();
MonitoringDataCollector appCollector = collector.group(appName);
Expand All @@ -231,29 +213,29 @@ private Map<String, List<HealthCheckResponse>> collectChecks(MonitoringDataColle
Set<String> appCollected = collected.get(appName);
// prevent adding same check more then once, unfortunately we have to run it to find that out
if (appCollected == null || !appCollected.contains(metric)) {
stateByApp.computeIfAbsent(appName, key -> new ArrayList<>()).add(response);
statusByApp.computeIfAbsent(appName, key -> new ArrayList<>()).add(response);
collectUpDown(appCollector, response);
if (response.getState() == State.DOWN && response.getData().isPresent()) {
if (response.getStatus() == Status.DOWN && response.getData().isPresent()) {
appCollector.annotate(metric, 0L, createAnnotation(response.getData().get()));
}
collected.computeIfAbsent(appName, key -> new HashSet<>()).add(metric);
}
}
}
return stateByApp;
return statusByApp;
}

private static HealthCheckResponse computeJointState(String name, Collection<HealthCheckResponse> responses) {
long ups = responses.stream().filter(response -> response.getState() == State.UP).count();
long ups = responses.stream().filter(response -> response.getStatus() == Status.UP).count();
if (ups == responses.size()) {
return HealthCheckResponse.up(name);
}
String upNames = responses.stream()
.filter(r -> r.getState() == State.UP)
.filter(r -> r.getStatus() == Status.UP)
.map(r -> r.getName())
.collect(joining(","));
String downNames = responses.stream()
.filter(r -> r.getState() == State.DOWN)
.filter(r -> r.getStatus() == Status.DOWN)
.map(r -> r.getName())
.collect(joining(","));
return HealthCheckResponse.named(name).down()
Expand All @@ -275,7 +257,7 @@ private static String[] createAnnotation(Map<String, Object> data) {
}

@Override
public void event(Event event) {
public void event(Event<?> event) {
// Remove healthchecks when the app is undeployed.
Deployment.APPLICATION_UNLOADED.onMatch(event, appInfo -> unregisterHealthCheck(appInfo.getName()));

Expand Down Expand Up @@ -341,7 +323,6 @@ public void registerHealthCheck(String healthCheckName, HealthCheck healthCheck,
public void unregisterHealthCheck(String appName) {
readiness.remove(appName);
liveness.remove(appName);
health.remove(appName);
applicationClassLoaders.remove(appName);
applicationsLoaded.remove(appName);
}
Expand All @@ -367,15 +348,19 @@ public void registerClassLoader(String appName, ClassLoader classloader) {
}

private Map<String, Set<HealthCheck>> getHealthChecks(HealthCheckType type) {
final Map<String, Set<HealthCheck>> healthChecks;
if (type == READINESS) {
healthChecks = readiness;
} else if (type == LIVENESS) {
healthChecks = liveness;
} else {
healthChecks = health;
if (type == null) {
type = HealthCheckType.UNKNOWN;
}
switch (type) {
case LIVENESS:
return liveness;
case READINESS:
return readiness;
case UNKNOWN:
default:
LOG.warning("Unrecognised HealthCheckType: " + type);
return new HashMap<>();
}
return healthChecks;
}

private Map<String, Set<HealthCheck>> getCollectiveHealthChecks(HealthCheckType type) {
Expand All @@ -385,7 +370,7 @@ private Map<String, Set<HealthCheck>> getCollectiveHealthChecks(HealthCheckType
} else if (type == LIVENESS) {
healthChecks = liveness;
} else {
healthChecks = new HashMap<>(health);
healthChecks = new HashMap<>();
BiConsumer<? super String, ? super Set<HealthCheck>> mergeHealthCheckMap
= (key, value) -> healthChecks.merge(key, value, (oldValue, newValue) -> {
oldValue.addAll(newValue);
Expand Down Expand Up @@ -463,12 +448,9 @@ private void constructResponse(HttpServletResponse httpResponse,
for (HealthCheckResponse healthCheckResponse : healthCheckResponses) {
JsonObjectBuilder healthCheckObject = Json.createObjectBuilder();

// Add the name and state
// Add the name and status
healthCheckObject.add("name", healthCheckResponse.getName());
healthCheckObject.add(
backwardCompEnabled && type == HEALTH ? "state" : "status",
healthCheckResponse.getState().toString()
);
healthCheckObject.add("status", healthCheckResponse.getStatus().toString());

// Add data if present
JsonObjectBuilder healthCheckData = Json.createObjectBuilder();
Expand All @@ -484,7 +466,7 @@ private void constructResponse(HttpServletResponse httpResponse,

// Check if we need to set the response as 503. Check against status 200 so we don't repeatedly set it
if (httpResponse.getStatus() == 200
&& healthCheckResponse.getState().equals(HealthCheckResponse.State.DOWN)) {
&& healthCheckResponse.getStatus().equals(HealthCheckResponse.Status.DOWN)) {
httpResponse.setStatus(503);
}
}
Expand All @@ -493,10 +475,7 @@ private void constructResponse(HttpServletResponse httpResponse,
JsonObjectBuilder responseObject = Json.createObjectBuilder();

// Set the aggregate status
responseObject.add(
backwardCompEnabled && type == HEALTH ? "outcome" : "status",
httpResponse.getStatus() == 200 ? "UP" : "DOWN"
);
responseObject.add("status", httpResponse.getStatus() == 200 ? "UP" : "DOWN");

// Add all of the checks
responseObject.add("checks", checksArray);
Expand Down
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) [2019] Payara Foundation and/or its affiliates. All rights reserved.
* Copyright (c) [2019-2020] Payara Foundation and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
Expand Down Expand Up @@ -41,39 +41,38 @@

import java.lang.annotation.Annotation;
import java.util.Set;

import javax.enterprise.util.AnnotationLiteral;
import org.eclipse.microprofile.health.Health;

import org.eclipse.microprofile.health.Liveness;
import org.eclipse.microprofile.health.Readiness;

public enum HealthCheckType {

READINESS("/ready", new AnnotationLiteral<Readiness>() {
}),
LIVENESS("/live", new AnnotationLiteral<Liveness>() {
}),
HEALTH(null, new AnnotationLiteral<Health>() {
});
READINESS("/ready", new Readiness.Literal()),
LIVENESS("/live", new Liveness.Literal()),
UNKNOWN(null, null);

String path;
AnnotationLiteral literal;
private String path;
private AnnotationLiteral<?> literal;

private HealthCheckType(String path, AnnotationLiteral literal) {
private HealthCheckType(String path, AnnotationLiteral<?> literal) {
this.path = path;
this.literal = literal;
}

public AnnotationLiteral getLiteral() {
public AnnotationLiteral<?> getLiteral() {
return literal;
}

public static HealthCheckType fromPath(String path) {
for (HealthCheckType value : values()) {
if (value.path != null && value.path.equals(path)) {
// If the path is equal (with or without the slash)
if (value.path != null && (value.path.equals(path) || value.path.substring(1).equals(path))) {
return value;
}
}
return HEALTH;
return UNKNOWN;
}

public static HealthCheckType fromQualifiers(Set<Annotation> qualifiers) {
Expand Down
Expand Up @@ -44,7 +44,7 @@

import com.sun.enterprise.config.serverbeans.Config;
import com.sun.enterprise.util.ColumnFormatter;
import fish.payara.microprofile.healthcheck.config.MetricsHealthCheckConfiguration;
import fish.payara.microprofile.healthcheck.config.MicroprofileHealthCheckConfiguration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
Expand Down Expand Up @@ -72,7 +72,7 @@
@ExecuteOn({RuntimeType.DAS, RuntimeType.INSTANCE})
@TargetType(value = {CommandTarget.DAS, CommandTarget.STANDALONE_INSTANCE, CommandTarget.CLUSTER, CommandTarget.CONFIG, CommandTarget.DEPLOYMENT_GROUP})
@RestEndpoints({
@RestEndpoint(configBean = MetricsHealthCheckConfiguration.class,
@RestEndpoint(configBean = MicroprofileHealthCheckConfiguration.class,
opType = RestEndpoint.OpType.GET,
path = "get-microprofile-healthcheck-configuration",
description = "Gets the Microprofile Health Check Configuration")
Expand All @@ -97,8 +97,8 @@ public void execute(AdminCommandContext context) {
return;
}

MetricsHealthCheckConfiguration healthCheckConfiguration = targetConfig
.getExtensionByType(MetricsHealthCheckConfiguration.class);
MicroprofileHealthCheckConfiguration healthCheckConfiguration = targetConfig
.getExtensionByType(MicroprofileHealthCheckConfiguration.class);

ColumnFormatter columnFormatter = new ColumnFormatter(OUTPUT_HEADERS);
Object[] outputValues = {
Expand Down