Adding health reports to microservices

duration 20 minutes

Prerequisites:

Explore how to report and check the health of a microservice with MicroProfile Health.

What you’ll learn

You will learn how to use MicroProfile Health to report the health status of microservices and take appropriate actions based on this report.

MicroProfile Health allows services to report their health, and it publishes the overall health status to a defined endpoint. A service reports UP if it is available and reports DOWN if it is unavailable. MicroProfile Health reports an individual service status at the endpoint and indicates the overall status as UP if all the services are UP. A service orchestrator can then use the health statuses to make decisions.

A service checks its own health by performing necessary self-checks and then reports its overall status by implementing the API provided by MicroProfile Health. A self-check can be a check on anything that the service needs, such as a dependency, a successful connection to an endpoint, a system property, a database connection, or the availability of required resources. MicroProfile offers checks for startup, liveness, and readiness.

You will add startup, liveness, and readiness checks to the system and inventory services, that are provided for you, and implement what is necessary to report health status by using MicroProfile Health.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-microprofile-health.git
cd guide-microprofile-health

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Before you begin, make sure you have all the necessary prerequisites.

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

To try out the application, first go to the finish directory and run the following Maven goal to build the application and deploy it to Open Liberty:

cd finish
mvn liberty:run

After you see the following message, your Liberty instance is ready:

The defaultServer server is ready to run a smarter planet.

The system and inventory services can be found at the following URLs:

Visit the http://localhost:9080/health URL to see the overall health status of the application, as well as the aggregated data of the startup, liveness and readiness checks. Three checks show the state of the system service, and the other three checks show the state of the inventory service. As you might expect, all services are in the UP state, and the overall health status of the application is in the UP state.

Access the /health/started endpoint by visiting the http://localhost:9080/health/started URL to view the data from the startup health checks. You can also access the /health/live endpoint by visiting the http://localhost:9080/health/live URL to view the data from the liveness health checks. Similarly, access the /health/ready endpoint by visiting the http://localhost:9080/health/ready URL to view the data from the readiness health checks.

After you are finished checking out the application, stop the Liberty instance by pressing CTRL+C in the command-line session where you ran Liberty. Alternatively, you can run the liberty:stop goal from the finish directory in another shell session:

mvn liberty:stop

Adding health checks to microservices

Navigate to the start directory to begin.

When you run Open Liberty in dev mode, dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:

mvn liberty:dev

After you see the following message, your Liberty instance is ready in dev mode:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

A health report will be generated automatically for all services that enable MicroProfile Health. The mpHealth feature has already been enabled for you in the src/main/liberty/config/server.xml file.

All services must provide an implementation of the HealthCheck interface, which is used to verify their health. MicroProfile Health offers health checks for startup, liveness, and readiness. A startup check allows applications to define startup probes that are used for initial verification of the application before the Liveness probe takes over. For example, a startup check might check which applications require additional startup time on their first initialization. A liveness check allows third-party services to determine whether a microservice is running. If the liveness check fails, the application can be terminated. For example, a liveness check might fail if the application runs out of memory. A readiness check allows third-party services, such as Kubernetes, to determine whether a microservice is ready to process requests. For example, a readiness check might check dependencies, such as database connections.

server.xml

 1<server description="Sample Liberty server">
 2
 3  <featureManager>
 4    <feature>restfulWS-3.1</feature>
 5    <feature>jsonb-3.0</feature>
 6    <feature>jsonp-2.1</feature>
 7    <feature>cdi-4.0</feature>
 8    <feature>mpConfig-3.1</feature>
 9    <feature>mpRestClient-3.0</feature>
10    <!-- tag::mpHealth[] -->
11    <feature>mpHealth-4.0</feature>
12    <!-- end::mpHealth[] -->
13  </featureManager>
14
15  <variable name="http.port" defaultValue="9080"/>
16  <variable name="https.port" defaultValue="9443"/>
17
18  <httpEndpoint host="*" httpPort="${http.port}"
19    httpsPort="${https.port}" id="defaultHttpEndpoint"/>
20
21  <webApplication location="guide-microprofile-health.war" contextRoot="/"/>
22</server>

Adding health checks to the system service

Create the SystemStartupCheck class.
src/main/java/io/openliberty/guides/system/SystemStartupCheck.java

SystemStartupCheck.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12// tag::SystemStartupCheck[]
13package io.openliberty.guides.system;
14
15import java.lang.management.ManagementFactory;
16import com.sun.management.OperatingSystemMXBean;
17import jakarta.enterprise.context.ApplicationScoped;
18import org.eclipse.microprofile.health.Startup;
19import org.eclipse.microprofile.health.HealthCheck;
20import org.eclipse.microprofile.health.HealthCheckResponse;
21
22// tag::Startup[]
23@Startup
24// end::Startup[]
25@ApplicationScoped
26public class SystemStartupCheck implements HealthCheck {
27
28    @Override
29    public HealthCheckResponse call() {
30        OperatingSystemMXBean bean = (com.sun.management.OperatingSystemMXBean)
31        ManagementFactory.getOperatingSystemMXBean();
32        double cpuUsed = bean.getSystemCpuLoad();
33        String cpuUsage = String.valueOf(cpuUsed);
34        return HealthCheckResponse.named(SystemResource.class
35                                            .getSimpleName() + " Startup Check")
36                                            .status(cpuUsed < 0.95).build();
37    }
38}
39
40// end::SystemStartupCheck[]

The @Startup annotation indicates that this class is a startup health check procedure. In this case, you are checking the cpu usage. If more than 95% of the cpu is being used, a status of DOWN is returned.

Create the SystemLivenessCheck class.
src/main/java/io/openliberty/guides/system/SystemLivenessCheck.java

SystemLivenessCheck.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12// tag::SystemLivenessCheck[]
13package io.openliberty.guides.system;
14
15import java.lang.management.ManagementFactory;
16import java.lang.management.MemoryMXBean;
17
18import jakarta.enterprise.context.ApplicationScoped;
19import org.eclipse.microprofile.health.Liveness;
20import org.eclipse.microprofile.health.HealthCheck;
21import org.eclipse.microprofile.health.HealthCheckResponse;
22
23// tag::Liveness[]
24@Liveness
25// end::Liveness[]
26@ApplicationScoped
27public class SystemLivenessCheck implements HealthCheck {
28
29  @Override
30  public HealthCheckResponse call() {
31    MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
32    long memUsed = memBean.getHeapMemoryUsage().getUsed();
33    long memMax = memBean.getHeapMemoryUsage().getMax();
34
35    return HealthCheckResponse.named(
36      SystemResource.class.getSimpleName() + " Liveness Check")
37                              .status(memUsed < memMax * 0.9).build();
38  }
39}
40// end::SystemLivenessCheck[]

The @Liveness annotation indicates that this class is a liveness health check procedure. In this case, you are checking the heap memory usage. If more than 90% of the maximum memory is being used, a status of DOWN is returned.

Create the SystemReadinessCheck class.
src/main/java/io/openliberty/guides/system/SystemReadinessCheck.java

SystemReadinessCheck.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12// tag::SystemReadinessCheck[]
13package io.openliberty.guides.system;
14
15import jakarta.enterprise.context.ApplicationScoped;
16import org.eclipse.microprofile.health.Readiness;
17import org.eclipse.microprofile.health.HealthCheck;
18import org.eclipse.microprofile.health.HealthCheckResponse;
19
20// tag::Readiness[]
21@Readiness
22// end::Readiness[]
23// tag::ApplicationScoped[]
24@ApplicationScoped
25// end::ApplicationScoped[]
26public class SystemReadinessCheck implements HealthCheck {
27
28  private static final String READINESS_CHECK = SystemResource.class.getSimpleName()
29                                               + " Readiness Check";
30  @Override
31// tag::healthCheckResponse[]
32  public HealthCheckResponse call() {
33    // tag::defaultServer[]
34    if (!System.getProperty("wlp.server.name").equals("defaultServer")) {
35    // end::defaultServer[]
36      // tag::HealthCheckResponse-DOWN[]
37      // tag::HealthCheckResponse-named[]
38      return HealthCheckResponse.down(READINESS_CHECK);
39      // end::HealthCheckResponse-named[]
40      // end::HealthCheckResponse-DOWN[]
41    }
42    // tag::HealthCheckResponse-UP[]
43    return HealthCheckResponse.up(READINESS_CHECK);
44    // end::HealthCheckResponse-UP[]
45  }
46// end::healthCheckResponse[]
47}
48// end::SystemReadinessCheck[]

The @Readiness annotation indicates that this class is a readiness health check procedure. By pairing this annotation with the ApplicationScoped context from the Contexts and Dependency Injections API, the bean is discovered automatically when the http://localhost:9080/health endpoint receives a request.

The call() method is used to return the health status of a particular service. In this case, you are checking if the server name is defaultServer and returning UP if it is, and DOWN otherwise. This example is a very simple implementation of the call() method. In a real environment, you would orchestrate more meaningful health checks.

Adding health checks to the inventory service

Create the InventoryStartupCheck class.
src/main/java/io/openliberty/guides/inventory/InventoryStartupCheck.java

InventoryStartupCheck.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12// tag::InventoryStartupCheck[]
13package io.openliberty.guides.inventory;
14
15import java.lang.management.ManagementFactory;
16import com.sun.management.OperatingSystemMXBean;
17import jakarta.enterprise.context.ApplicationScoped;
18import org.eclipse.microprofile.health.Startup;
19import org.eclipse.microprofile.health.HealthCheck;
20import org.eclipse.microprofile.health.HealthCheckResponse;
21
22// tag::Startup[]
23@Startup
24// end::Startup[]
25@ApplicationScoped
26public class InventoryStartupCheck implements HealthCheck {
27
28    @Override
29    public HealthCheckResponse call() {
30        OperatingSystemMXBean bean = (com.sun.management.OperatingSystemMXBean)
31        ManagementFactory.getOperatingSystemMXBean();
32        double cpuUsed = bean.getSystemCpuLoad();
33        String cpuUsage = String.valueOf(cpuUsed);
34        return HealthCheckResponse.named(InventoryResource.class
35                                            .getSimpleName() + " Startup Check")
36                                            .status(cpuUsed < 0.95).build();
37    }
38}
39
40// end::InventoryStartupCheck[]

This startup check verifies that the cpu usage is below 95%. If more than 95% of the cpu is being used, a status of DOWN is returned.

Create the InventoryLivenessCheck class.
src/main/java/io/openliberty/guides/inventory/InventoryLivenessCheck.java

InventoryLivenessCheck.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12// tag::InventoryLivenessCheck[]
13package io.openliberty.guides.inventory;
14
15import jakarta.enterprise.context.ApplicationScoped;
16
17import java.lang.management.MemoryMXBean;
18import java.lang.management.ManagementFactory;
19
20import org.eclipse.microprofile.health.Liveness;
21
22import org.eclipse.microprofile.health.HealthCheck;
23import org.eclipse.microprofile.health.HealthCheckResponse;
24
25@Liveness
26@ApplicationScoped
27public class InventoryLivenessCheck implements HealthCheck {
28  @Override
29  public HealthCheckResponse call() {
30      MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
31      long memUsed = memBean.getHeapMemoryUsage().getUsed();
32      long memMax = memBean.getHeapMemoryUsage().getMax();
33
34      return HealthCheckResponse.named(
35        InventoryResource.class.getSimpleName() + " Liveness Check")
36                                .status(memUsed < memMax * 0.9).build();
37  }
38}
39// end::InventoryLivenessCheck[]

As with the system liveness check, you are checking the heap memory usage. If more than 90% of the maximum memory is being used, a DOWN status is returned.

Create the InventoryReadinessCheck class.
src/main/java/io/openliberty/guides/inventory/InventoryReadinessCheck.java

InventoryReadinessCheck.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12// tag::InventoryReadinessCheck[]
13package io.openliberty.guides.inventory;
14
15import jakarta.enterprise.context.ApplicationScoped;
16import jakarta.inject.Inject;
17import jakarta.ws.rs.client.Client;
18import jakarta.ws.rs.client.ClientBuilder;
19import jakarta.ws.rs.core.MediaType;
20import jakarta.ws.rs.core.Response;
21import org.eclipse.microprofile.health.Readiness;
22import org.eclipse.microprofile.health.HealthCheck;
23import org.eclipse.microprofile.health.HealthCheckResponse;
24
25@Readiness
26@ApplicationScoped
27public class InventoryReadinessCheck implements HealthCheck {
28
29  private static final String READINESS_CHECK = InventoryResource.class.getSimpleName()
30                                               + " Readiness Check";
31  @Inject
32  // tag::inventoryConfig[]
33  InventoryConfig config;
34  // end::inventoryConfig[]
35
36  // tag::isHealthy[]
37  public boolean isHealthy() {
38    if (config.isInMaintenance()) {
39      return false;
40    }
41    try {
42      String url = InventoryUtils.buildUrl("http", "localhost", config.getPortNumber(),
43          "/system/properties");
44      Client client = ClientBuilder.newClient();
45      // tag::getRequest[]
46      Response response = client.target(url).request(MediaType.APPLICATION_JSON).get();
47      // end::getRequest[]
48      if (response.getStatus() != 200) {
49        return false;
50      }
51      return true;
52    } catch (Exception e) {
53      return false;
54    }
55  }
56  // end::isHealthy[]
57
58  @Override
59  public HealthCheckResponse call() {
60    if (!isHealthy()) {
61      return HealthCheckResponse
62          .down(READINESS_CHECK);
63    }
64    return HealthCheckResponse
65        .up(READINESS_CHECK);
66  }
67
68}
69// end::InventoryReadinessCheck[]

In the isHealthy() method, you report the inventory service as not ready if the service is in maintenance or if its dependant service is unavailable.

For simplicity, the custom io_openliberty_guides_inventory_inMaintenance MicroProfile Config property, which is defined in the resources/CustomConfigSource.json file, indicates whether the service is in maintenance. This file was already created for you.

Moreover, the readiness health check procedure makes an HTTP GET request to the system service and checks its status. If the request is successful, the inventory service is healthy and ready because its dependant service is available. Otherwise, the inventory service is not ready and an unhealthy readiness status is returned.

If you are curious about the injected inventoryConfig object or if you want to learn more about MicroProfile Config, see Configuring microservices.

CustomConfigSource.json

1{
2    "config_ordinal":700,
3    "io_openliberty_guides_inventory_inMaintenance":false
4}

Running the application

You started the Open Liberty in dev mode at the beginning of the guide, so all the changes were automatically picked up.

While the Liberty is running, navigate to the http://localhost:9080/health URL to find the aggregated startup, liveness, and readiness health reports on the two services.

You can also navigate to the http://localhost:9080/health/started URL to view the startup health report, to the http://localhost:9080/health/live URL to view the liveness health report or the http://localhost:9080/health/ready URL to view the readiness health report.

Put the inventory service in maintenance by setting the io_openliberty_guides_inventory_inMaintenance property to true in the CustomConfigSource.json file.

Replace the CustomConfigSource.json file.
resources/CustomConfigSource.json

CustomConfigSource.json

1{
2    "config_ordinal":700,
3    "io_openliberty_guides_inventory_inMaintenance":true
4}

Because this configuration file is picked up dynamically, simply refresh the http://localhost:9080/health URL to see that the state of the inventory service changed to DOWN. The overall state of the application also changed to DOWN as a result. Go to the http://localhost:9080/inventory/systems URL to verify that the inventory service is indeed in maintenance. Set the io_openliberty_guides_inventory_inMaintenance property back to false after you are done.

Testing health checks

You will implement several test methods to validate the health of the system and inventory services.

Create the HealthIT class.
src/test/java/it/io/openliberty/guides/health/HealthIT.java

HealthIT.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2018, 2022 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12// tag::HealthIT[]
 13package it.io.openliberty.guides.health;
 14
 15import static org.junit.jupiter.api.Assertions.assertEquals;
 16
 17import java.beans.Transient;
 18import java.util.HashMap;
 19
 20import jakarta.json.JsonArray;
 21
 22import org.junit.jupiter.api.AfterEach;
 23import org.junit.jupiter.api.BeforeEach;
 24import org.junit.jupiter.api.Test;
 25
 26public class HealthIT {
 27
 28  private JsonArray servicesStates;
 29  private static HashMap<String, String> endpointData;
 30
 31  private String HEALTH_ENDPOINT = "health";
 32  private String READINESS_ENDPOINT = "health/ready";
 33  private String LIVENES_ENDPOINT = "health/live";
 34  private String STARTUP_ENDPOINT = "health/started";
 35
 36  @BeforeEach
 37  public void setup() {
 38    endpointData = new HashMap<String, String>();
 39  }
 40
 41  @Test
 42  // tag::testStartup[]
 43  public void testStartup() {
 44    endpointData.put("InventoryResource Startup Check", "UP");
 45    endpointData.put("SystemResource Startup Check", "UP");
 46
 47    servicesStates = HealthITUtil.connectToHealthEnpoint(200, STARTUP_ENDPOINT);
 48    checkStates(endpointData, servicesStates);
 49  }
 50  // end::testStartup[]
 51
 52  @Test
 53  // tag::testLiveness[]
 54  public void testLiveness() {
 55    endpointData.put("SystemResource Liveness Check", "UP");
 56    endpointData.put("InventoryResource Liveness Check", "UP");
 57
 58    servicesStates = HealthITUtil.connectToHealthEnpoint(200, LIVENES_ENDPOINT);
 59    checkStates(endpointData, servicesStates);
 60  }
 61  // end::testLiveness[]
 62
 63  @Test
 64  // tag::testReadiness[]
 65  public void testReadiness() {
 66    endpointData.put("SystemResource Readiness Check", "UP");
 67    endpointData.put("InventoryResource Readiness Check", "UP");
 68
 69    servicesStates = HealthITUtil.connectToHealthEnpoint(200, READINESS_ENDPOINT);
 70    checkStates(endpointData, servicesStates);
 71  }
 72  // end::testReadiness[]
 73
 74  @Test
 75  // tag::testHealth[]
 76  public void testHealth() {
 77    endpointData.put("SystemResource Startup Check", "UP");
 78    endpointData.put("SystemResource Liveness Check", "UP");
 79    endpointData.put("SystemResource Readiness Check", "UP");
 80    endpointData.put("InventoryResource Startup Check", "UP");
 81    endpointData.put("InventoryResource Liveness Check", "UP");
 82    endpointData.put("InventoryResource Readiness Check", "UP");
 83
 84    servicesStates = HealthITUtil.connectToHealthEnpoint(200, HEALTH_ENDPOINT);
 85    checkStates(endpointData, servicesStates);
 86
 87    endpointData.put("InventoryResource Readiness Check", "DOWN");
 88    HealthITUtil.changeInventoryProperty(HealthITUtil.INV_MAINTENANCE_FALSE,
 89        HealthITUtil.INV_MAINTENANCE_TRUE);
 90    servicesStates = HealthITUtil.connectToHealthEnpoint(503, HEALTH_ENDPOINT);
 91    checkStates(endpointData, servicesStates);
 92  }
 93  // end::testHealth[]
 94
 95  private void checkStates(HashMap<String, String> testData, JsonArray servStates) {
 96    testData.forEach((service, expectedState) -> {
 97      assertEquals(expectedState, HealthITUtil.getActualState(service, servStates),
 98          "The state of " + service + " service is not matching.");
 99    });
100  }
101
102  @AfterEach
103  public void teardown() {
104    HealthITUtil.cleanUp();
105  }
106
107}
108// end::HealthIT[]

Let’s break down the test cases:

  • The testStartup() test case compares the generated health report for the startup checks with the actual status of the services.

  • The testLiveness() test case compares the generated health report for the liveness checks with the actual status of the services.

  • The testReadiness() test case compares the generated health report for the readiness checks with the actual status of the services.

  • The testHealth() test case compares the generated health report with the actual status of the services. This test also puts the inventory service in maintenance by setting the io_openliberty_guides_inventory_inMaintenance property to true and comparing the generated health report with the actual status of the services.

A few more tests were included to verify the basic functionality of the system and inventory services. They can be found under the src/test/java/it/io/openliberty/guides/inventory/InventoryEndpointIT.java and src/test/java/it/io/openliberty/guides/system/SystemEndpointIT.java files. If a test failure occurs, then you might have introduced a bug into the code. These tests run automatically as a part of the integration test suite.

CustomConfigSource.json

1{
2    "config_ordinal":700,
3    "io_openliberty_guides_inventory_inMaintenance":false
4}

InventoryEndpointIT.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2017, 2024 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12// tag::testClass[]
 13package it.io.openliberty.guides.inventory;
 14
 15import static org.junit.jupiter.api.Assertions.assertEquals;
 16import static org.junit.jupiter.api.Assertions.assertTrue;
 17
 18import jakarta.json.JsonArray;
 19import jakarta.json.JsonObject;
 20import jakarta.ws.rs.client.Client;
 21import jakarta.ws.rs.client.ClientBuilder;
 22import jakarta.ws.rs.core.MediaType;
 23import jakarta.ws.rs.core.Response;
 24
 25import org.junit.jupiter.api.AfterEach;
 26import org.junit.jupiter.api.BeforeAll;
 27import org.junit.jupiter.api.BeforeEach;
 28import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
 29import org.junit.jupiter.api.Order;
 30import org.junit.jupiter.api.Test;
 31import org.junit.jupiter.api.TestMethodOrder;
 32
 33@TestMethodOrder(OrderAnnotation.class)
 34public class InventoryEndpointIT {
 35
 36  private static String port;
 37  private static String baseUrl;
 38
 39  private Client client;
 40
 41  private final String SYSTEM_PROPERTIES = "system/properties";
 42  private final String INVENTORY_SYSTEMS = "inventory/systems";
 43
 44  @BeforeAll
 45  public static void oneTimeSetup() {
 46    port = System.getProperty("http.port");
 47    baseUrl = "http://localhost:" + port + "/";
 48  }
 49
 50  @BeforeEach
 51  public void setup() {
 52    client = ClientBuilder.newClient();
 53  }
 54
 55  @AfterEach
 56  public void teardown() {
 57    client.close();
 58  }
 59
 60  // tag::tests[]
 61  @Test
 62  @Order(1)
 63  // tag::testHostRegistration[]
 64  public void testHostRegistration() {
 65    this.visitLocalhost();
 66
 67    Response response = this.getResponse(baseUrl + INVENTORY_SYSTEMS);
 68    this.assertResponse(baseUrl, response);
 69
 70    JsonObject obj = response.readEntity(JsonObject.class);
 71
 72    JsonArray systems = obj.getJsonArray("systems");
 73
 74    boolean localhostExists = false;
 75    for (int n = 0; n < systems.size(); n++) {
 76      localhostExists = systems.getJsonObject(n).get("hostname").toString()
 77          .contains("localhost");
 78      if (localhostExists) {
 79        break;
 80      }
 81    }
 82    assertTrue(localhostExists, "A host was registered, but it was not localhost");
 83
 84    response.close();
 85  }
 86  // end::testHostRegistration[]
 87
 88  @Test
 89  @Order(2)
 90  // tag::testSystemPropertiesMatch[]
 91  public void testSystemPropertiesMatch() {
 92    Response invResponse = this.getResponse(baseUrl + INVENTORY_SYSTEMS);
 93    Response sysResponse = this.getResponse(baseUrl + SYSTEM_PROPERTIES);
 94
 95    this.assertResponse(baseUrl, invResponse);
 96    this.assertResponse(baseUrl, sysResponse);
 97
 98    JsonObject jsonFromInventory = (JsonObject) invResponse.readEntity(JsonObject.class)
 99                                                           .getJsonArray("systems")
100                                                           .getJsonObject(0)
101                                                           .get("properties");
102
103    JsonObject jsonFromSystem = sysResponse.readEntity(JsonObject.class);
104
105    String osNameFromInventory = jsonFromInventory.getString("os.name");
106    String osNameFromSystem = jsonFromSystem.getString("os.name");
107    this.assertProperty("os.name", "localhost", osNameFromSystem,
108                        osNameFromInventory);
109
110    String userNameFromInventory = jsonFromInventory.getString("user.name");
111    String userNameFromSystem = jsonFromSystem.getString("user.name");
112    this.assertProperty("user.name", "localhost", userNameFromSystem,
113                        userNameFromInventory);
114
115    invResponse.close();
116    sysResponse.close();
117  }
118  // end::testSystemPropertiesMatch[]
119
120  @Test
121  @Order(3)
122  // tag::testUnknownHost[]
123  public void testUnknownHost() {
124    Response response = this.getResponse(baseUrl + INVENTORY_SYSTEMS);
125    this.assertResponse(baseUrl, response);
126
127    Response badResponse = client.target(baseUrl + INVENTORY_SYSTEMS + "/"
128        + "badhostname").request(MediaType.APPLICATION_JSON).get();
129
130    String obj = badResponse.readEntity(String.class);
131
132    boolean isError = obj.contains("error");
133    assertTrue(isError,
134        "badhostname is not a valid host but it didn't raise an error");
135
136    response.close();
137    badResponse.close();
138  }
139
140  // end::testUnknownHost[]
141  // end::tests[]
142  // tag::helpers[]
143  // tag::javadoc[]
144  /**
145   * <p>
146   * Returns response information from the specified URL.
147   * </p>
148   *
149   * @param url
150   *          - target URL.
151   * @return Response object with the response from the specified URL.
152   */
153  // end::javadoc[]
154  private Response getResponse(String url) {
155    return client.target(url).request().get();
156  }
157
158  // tag::javadoc[]
159  /**
160   * <p>
161   * Asserts that the given URL has the correct response code of 200.
162   * </p>
163   *
164   * @param url
165   *          - target URL.
166   * @param response
167   *          - response received from the target URL.
168   */
169  // end::javadoc[]
170  private void assertResponse(String url, Response response) {
171    assertEquals(200, response.getStatus(), "Incorrect response code from " + url);
172  }
173
174  // tag::javadoc[]
175  /**
176   * Asserts that the specified JVM system property is equivalent in both the
177   * system and inventory services.
178   *
179   * @param propertyName
180   *          - name of the system property to check.
181   * @param hostname
182   *          - name of JVM's host.
183   * @param expected
184   *          - expected name.
185   * @param actual
186   *          - actual name.
187   */
188  // end::javadoc[]
189  private void assertProperty(String propertyName, String hostname,
190      String expected, String actual) {
191    assertEquals(expected, actual, "JVM system property [" + propertyName + "] "
192        + "in the system service does not match the one stored in "
193        + "the inventory service for " + hostname);
194  }
195
196  // tag::javadoc[]
197  /**
198   * Makes a simple GET request to inventory/localhost.
199   */
200  // end::javadoc[]
201  private void visitLocalhost() {
202    Response response = this.getResponse(baseUrl + SYSTEM_PROPERTIES);
203    this.assertResponse(baseUrl, response);
204    response.close();
205
206    Response targetResponse = client.target(baseUrl + INVENTORY_SYSTEMS
207        + "/localhost").request().get();
208    targetResponse.close();
209  }
210  // end::helpers[]
211}
212// end::testClass[]

SystemEndpointIT.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2024 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package it.io.openliberty.guides.system;
13
14import static org.junit.jupiter.api.Assertions.assertEquals;
15
16import jakarta.json.JsonObject;
17import jakarta.ws.rs.client.Client;
18import jakarta.ws.rs.client.ClientBuilder;
19import jakarta.ws.rs.client.WebTarget;
20import jakarta.ws.rs.core.Response;
21
22import org.junit.jupiter.api.Test;
23
24public class SystemEndpointIT {
25
26  @Test
27  public void testGetProperties() {
28    String port = System.getProperty("http.port");
29    String url = "http://localhost:" + port + "/";
30
31    Client client = ClientBuilder.newClient();
32
33    WebTarget target = client.target(url + "system/properties");
34    Response response = target.request().get();
35
36    assertEquals(200, response.getStatus(), "Incorrect response code from " + url);
37
38    JsonObject obj = response.readEntity(JsonObject.class);
39
40    assertEquals(System.getProperty("os.name"), obj.getString("os.name"),
41        "The system property for the local and remote JVM should match");
42
43    response.close();
44    client.close();
45  }
46}

Running the tests

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode.

You see the following output:

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running it.io.openliberty.guides.health.HealthIT
[INFO] [WARNING ] CWMMH0052W: The class io.openliberty.microprofile.health30.impl.HealthCheck30ResponseImpl implementing HealthCheckResponse in the guide-microprofile-health application in module guide-microprofile-health.war, reported a DOWN status with data Optional[{}].
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.706 s - in it.io.openliberty.guides.health.HealthIT
[INFO] Running it.io.openliberty.guides.system.SystemEndpointIT
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 s - in it.io.openliberty.guides.system.SystemEndpointIT
[INFO] Running it.io.openliberty.guides.inventory.InventoryEndpointIT
[INFO] [WARNING ] Interceptor for {http://client.inventory.guides.openliberty.io/}SystemClient has thrown exception, unwinding now
[INFO] Could not send Message.
[INFO] [err] The specified host is unknown.
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.171 s - in it.io.openliberty.guides.inventory.InventoryEndpointIT
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0

The warning messages are expected. The first warning results from a request to a service that is under maintenance. This request is made in the testHealth() test from the InventoryEndpointIT integration test. The second warning and error results from a request to a bad or an unknown hostname. This request is made in the testUnknownHost() test from the InventoryEndpointIT integration test.

The tests might fail if your system CPU or memory use is high. The status of the system is DOWN if the CPU usage is over 95%, or the memory usage is over 90%.

To see whether the tests detect a failure, manually change the configuration of io_openliberty_guides_inventory_inMaintenance from false to true in the resources/CustomConfigSource.json file. Rerun the tests to see a test failure occur. The test failure occurs because the initial status of the inventory service is DOWN.

When you are done checking out the service, exit dev mode by pressing CTRL+C in the command-line session where you ran Liberty.

Great work! You’re done!

You just learned how to add health checks to report the states of microservices by using MicroProfile Health in Open Liberty. Then, you wrote tests to validate the generated health report.

Feel free to try one of the related MicroProfile guides. They demonstrate additional technologies that you can learn and expand on top of what you built here.

Guide Attribution

Adding health reports to microservices by Open Liberty is licensed under CC BY-ND 4.0

Copy file contents
Copied to clipboard

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star