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

Implement health endpoint based on MicroProfile Health #480

Merged
merged 3 commits into from
Mar 31, 2023

Conversation

nscuro
Copy link
Collaborator

@nscuro nscuro commented Mar 29, 2023

This PR implements health endpoints according to the MicroProfile Health specification.

The new HealthServlet is capable of handling the following request paths:

  • / - Will return results of all registered health checks
  • /live - Will return only results of registered liveness checks
  • /ready - Will return only results of registered readiness checks
  • /started - Will return only results of registered startup checks

See also: MicroProfile Health REST interface specification

Individual checks must implement the HealthCheck interface, and must be annotated with either @Liveness, @Readiness, @Startup, or a combination of the same. See Different Kinds of Health Checks.

Implementations of HealthCheck must be registered with a global HealthCheckRegistry instance, e.g.:

HealthCheckRegistry.getInstance().register("some-id", new CustomHealthCheck());

A readiness check for database connections is included in this PR.

Applications built with Alpine can add their own custom checks by following the same pattern:

  1. Implement a HealthCheck (here: a liveness check)
package org.dependencytrack.health;

import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Liveness;

@Liveness
public class CustomHealthCheck implements HealthCheck {
    @Override
    public HealthCheckResponse call() {
        return HealthCheckResponse.builder()
                .name("custom-check")
                .up()
                .withData("some", "data")
                .build();
    }
}
  1. Register desired health checks with HealthCheckRegistry, i.e. in a ServletContextListener
package org.dependencytrack.health;

import alpine.common.logging.Logger;
import alpine.server.health.HealthCheckRegistry;
import alpine.server.health.checks.DatabaseHealthCheck;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class HealthCheckInitializer implements ServletContextListener {

    private static final Logger LOGGER = Logger.getLogger(HealthCheckInitializer.class);

    @Override
    public void contextInitialized(final ServletContextEvent event) {
        LOGGER.info("Registering health checks");
        HealthCheckRegistry.getInstance().register("database", new DatabaseHealthCheck());
        HealthCheckRegistry.getInstance().register("custom", new CustomHealthCheck());
    }

}
  1. Mount HealthServlet via web.xml
<servlet>
    <servlet-name>Health</servlet-name>
    <servlet-class>alpine.server.servlets.HealthServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>Health</servlet-name>
    <url-pattern>/health/*</url-pattern>
</servlet-mapping>

Example Responses

Given the database check included in this PR, and the example custom check shown above, responses of the different endpoints would be as follows:

  1. /
{
    "status": "UP",
    "checks": [
        {
            "name": "custom-check",
            "status": "UP",
            "data": {
                "some": "data"
            }
        },
        {
            "name": "database",
            "status": "UP",
            "data": {
                "nontx_connection_pool": "UP",
                "tx_connection_pool": "UP"
            }
        }
    ]
}
  1. /live
{
    "status": "UP",
    "checks": [
        {
            "name": "custom-check",
            "status": "UP",
            "data": {
                "some": "data"
            }
        }
    ]
}
  1. /ready
{
    "status": "UP",
    "checks": [
        {
            "name": "database",
            "status": "UP",
            "data": {
                "nontx_connection_pool": "UP",
                "tx_connection_pool": "UP"
            }
        }
    ]
}
  1. /started
{
    "status": "UP",
    "checks": []
}

As per the spec, the status codes returned by those endpoints are:

  • 200 - When all checks yield an UP response
  • 503 - When at least one check yields a DOWN response
  • 500 - When something failed while determining the status or executing checks

Addresses #22

Signed-off-by: nscuro <nscuro@protonmail.com>
@stevespringett
Copy link
Owner

Is this ready for merge?

@nscuro
Copy link
Collaborator Author

nscuro commented Mar 29, 2023

@stevespringett Yes, ready from my side.

Also:

* Allow checks to have multiple types, not just one
* Rename `HealthCheckServlet` to `HealthServlet`
* Add tests for servlet

Signed-off-by: nscuro <nscuro@protonmail.com>
@nscuro
Copy link
Collaborator Author

nscuro commented Mar 31, 2023

Changed the way health checks are registered to a global singleton registry pattern.

Service providers and ServiceLoader are a bit too unwieldy I think, and make it unnecessarily hard to test the servlet.

Updated PR description accordingly. Definitely ready to merge now.

Signed-off-by: nscuro <nscuro@protonmail.com>
@stevespringett stevespringett merged commit 6b07ef3 into stevespringett:master Mar 31, 2023
@nscuro nscuro deleted the health-check branch March 31, 2023 22:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants