Skip to content

Commit

Permalink
Merge pull request #4254 from jGauravGupta/PAYARA-3829
Browse files Browse the repository at this point in the history
PAYARA-3829 MicroProfile Healthcheck 2.1 implementation
  • Loading branch information
MarkWareham authored Oct 21, 2019
2 parents a9318d6 + 2e697d0 commit b590f73
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
*/
package fish.payara.microprofile.healthcheck;

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.beans.PropertyChangeEvent;
import java.io.IOException;
import java.util.ArrayList;
Expand All @@ -55,10 +58,12 @@
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.servlet.http.HttpServletResponse;

import fish.payara.microprofile.healthcheck.config.MetricsHealthCheckConfiguration;

import java.util.HashMap;
import java.util.Map.Entry;
import java.util.function.BiConsumer;
import static java.util.logging.Level.WARNING;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.glassfish.api.StartupRunLevel;
Expand Down Expand Up @@ -92,18 +97,28 @@ public class HealthCheckService implements EventListener, ConfigListener {
@Inject
MetricsHealthCheckConfiguration configuration;

private boolean backwardCompEnabled;

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

private final Map<String, Set<HealthCheck>> healthChecks = new ConcurrentHashMap<>();
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<>();

private final Map<String, ClassLoader> applicationClassLoaders = new ConcurrentHashMap<>();
private final List<String> applicationsLoaded = new CopyOnWriteArrayList<>();

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 = ConfigProvider.getConfig()
.getOptionalValue(BACKWARD_COMP_ENABLED_PROPERTY, Boolean.class)
.orElse(false);
}

@Override
Expand All @@ -112,9 +127,12 @@ public void event(Event event) {
if (event.is(Deployment.APPLICATION_UNLOADED)) {
ApplicationInfo appInfo = Deployment.APPLICATION_UNLOADED.getHook(event);
if (appInfo != null) {
healthChecks.remove(appInfo.getName());
applicationClassLoaders.remove(appInfo.getName());
applicationsLoaded.remove(appInfo.getName());
String appName = appInfo.getName();
readiness.remove(appName);
liveness.remove(appName);
health.remove(appName);
applicationClassLoaders.remove(appName);
applicationsLoaded.remove(appName);
}
}

Expand All @@ -134,15 +152,18 @@ public boolean isEnabled() {
public boolean isSecurityEnabled() {
return Boolean.parseBoolean(configuration.getSecurityEnabled());
}

/**
* Register a HealthCheck to the Set of HealthChecks to execute when performHealthChecks is called.
* Register a HealthCheck to the Set of HealthChecks based on appName to execute when
* performHealthChecks is called.
*
* @param appName The name of the application being deployed
* @param healthCheck The HealthCheck to register
*/
public void registerHealthCheck(String appName, HealthCheck healthCheck) {
// If we don't already have the app registered, we need to create a new Set for it
public void registerHealthCheck(String appName, HealthCheck healthCheck, HealthCheckType type) {
Map<String, Set<HealthCheck>> healthChecks = getHealthChecks(type);

// If we don't already have the app registered, we need to create a new Set for it
if (!healthChecks.containsKey(appName)) {
// Sync so that we don't get clashes
synchronized (this) {
Expand Down Expand Up @@ -177,18 +198,50 @@ 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;
}
return healthChecks;
}

private Map<String, Set<HealthCheck>> getCollectiveHealthChecks(HealthCheckType type) {
final Map<String, Set<HealthCheck>> healthChecks;
if (type == READINESS) {
healthChecks = readiness;
} else if (type == LIVENESS) {
healthChecks = liveness;
} else {
healthChecks = new HashMap<>(health);
BiConsumer<? super String, ? super Set<HealthCheck>> mergeHealthCheckMap
= (key, value) -> healthChecks.merge(key, value, (oldValue, newValue) -> {
oldValue.addAll(newValue);
return oldValue;
});
readiness.forEach(mergeHealthCheckMap);
liveness.forEach(mergeHealthCheckMap);
}
return healthChecks;
}

/**
* Execute the call method of every registered HealthCheck and generate the response.
*
* @param response The response to return
* @param type the type of health check
* @throws IOException If there's an issue writing the response
*/
public void performHealthChecks(HttpServletResponse response) throws IOException {
public void performHealthChecks(HttpServletResponse response, HealthCheckType type) throws IOException {
Set<HealthCheckResponse> healthCheckResponses = new HashSet<>();

// Iterate over every HealthCheck stored in the Map
for (Map.Entry<String, Set<HealthCheck>> healthChecksEntry : healthChecks.entrySet()) {
for (Entry<String, Set<HealthCheck>> healthChecksEntry : getCollectiveHealthChecks(type).entrySet()) {
for (HealthCheck healthCheck : healthChecksEntry.getValue()) {
// Execute the call method of the HealthCheck and add its outcome to the set of responses
try {
Expand All @@ -213,17 +266,23 @@ public void performHealthChecks(HttpServletResponse response) throws IOException
// No applications (yet), server is not ready.
if (applicationsLoaded.isEmpty()) {
// Application is not yet deployed
healthCheckResponses.add(HealthCheckResponse.builder().name("No Application deployed").down().build());
healthCheckResponses.add(
HealthCheckResponse.builder()
.name("No Application deployed")
.down()
.build()
);
}

// If we haven't encountered an exception, construct the JSON response
if (response.getStatus() != 500) {
constructResponse(response, healthCheckResponses);
constructResponse(response, healthCheckResponses, type);
}
}

private void constructResponse(HttpServletResponse httpResponse,
Set<HealthCheckResponse> healthCheckResponses) throws IOException {
Set<HealthCheckResponse> healthCheckResponses,
HealthCheckType type) throws IOException {
httpResponse.setContentType("application/json");

// For each HealthCheckResponse we got from executing the health checks...
Expand All @@ -233,7 +292,10 @@ private void constructResponse(HttpServletResponse httpResponse,

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

// Add data if present
JsonObjectBuilder healthCheckData = Json.createObjectBuilder();
Expand All @@ -257,12 +319,11 @@ private void constructResponse(HttpServletResponse httpResponse,
// Create the final aggregate object
JsonObjectBuilder responseObject = Json.createObjectBuilder();

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

// Add all of the checks
responseObject.add("checks", checksArray);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) [2019] 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
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://github.com/payara/Payara/blob/master/LICENSE.txt
* See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* The Payara Foundation designates this particular file as subject to the "Classpath"
* exception as provided by the Payara Foundation in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package fish.payara.microprofile.healthcheck;

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>() {
});

String path;
AnnotationLiteral literal;

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

public AnnotationLiteral getLiteral() {
return literal;
}

public static HealthCheckType fromPath(String path) {
for (HealthCheckType value : values()) {
if (value.path != null && value.path.equals(path)) {
return value;
}
}
return HEALTH;
}

public static HealthCheckType fromQualifiers(Set<Annotation> qualifiers) {
for (HealthCheckType value : values()) {
if (qualifiers != null && qualifiers.contains(value.literal)) {
return value;
}
}
throw new IllegalStateException("HealthCheckType not found for : " + qualifiers);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
package fish.payara.microprofile.healthcheck.servlet;

import fish.payara.microprofile.healthcheck.HealthCheckService;
import fish.payara.microprofile.healthcheck.HealthCheckType;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
Expand All @@ -53,7 +54,7 @@
* @author Andrew Pielage
*/
public class HealthCheckServlet extends HttpServlet {

/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
*
Expand All @@ -75,7 +76,9 @@ protected void processRequest(HttpServletRequest request, HttpServletResponse re
response.sendError(SC_FORBIDDEN, "MicroProfile Health Check Service is disabled");
return;
}
healthCheckService.performHealthChecks(response);

healthCheckService.performHealthChecks(response, HealthCheckType.fromPath(request.getPathInfo()));

}

// <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
Expand Down Expand Up @@ -114,7 +117,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
*/
@Override
public String getServletInfo() {
return "Short description";
return "HealthCheck Endpoint";
}// </editor-fold>

}
Loading

0 comments on commit b590f73

Please sign in to comment.