Skip to content

Commit

Permalink
ID-2377: added utils for health checks (#23)
Browse files Browse the repository at this point in the history
* added util methods for health checks

*  readme details

* upgrade to java 17 and configurable down response

* bytte ut lombok med records
  • Loading branch information
annemarte authored Jan 13, 2023
1 parent e4d930a commit 76e8529
Show file tree
Hide file tree
Showing 15 changed files with 395 additions and 41 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/call-maventests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 1.8
java-version: 17
- name: Cache Maven packages
uses: actions/cache@v2
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish-test-results.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 1.8
java-version: 17
- name: Cache Maven packages
uses: actions/cache@v2
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:

steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 1.8
java-version: 17

- name: Deploy to Github Package Registry
env:
Expand Down
67 changes: 62 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,69 @@ endpoints still work as intended
- remember to check if the build-info goal is set as described in the Configuration section
- ***IMPORTANT:*** Add minimum the following configuration in your application.yaml:
```
management.server.port=8090
management.endpoints.web.base-path=/
management.endpoints.web.exposure.include=info,version,prometheus,health
management:
server:
port: 8090
endpoints:
web:
exposure:
include: info,version,prometheus,health
base-path: /
```
- TESTS: there might be issues with the tests if you don't have a "root" application.yaml in your test resources, since the management.server.port is set to other than default. It's safest to set ```management.server.port=``` in the test application.yaml to make sure it's set to a random port and that you don't need as many test adjustments.
- you _may_ use the IDPortenActuatorWebSecurityProperties in place of the WebSecurityProperties, for simple access management

- TESTS: there might be issues with the tests if you don't have a "root" application.yaml in your test resources, since
the management.server.port is set to other than default. It's safest to set ```management.server.port=``` in the test
application.yaml to make sure it's set to a random port and that you don't need as many test adjustments.
- you _may_ use the IDPortenActuatorWebSecurityProperties in place of the WebSecurityProperties, for simple access
management
- default config is idporten-actuators.security.allowed-list=/health/**,/version,/info,/prometheus, but you can
override this in your application.yaml if you want to use the utility class

## Configuring up some simple http health checks

If your application has external dependencies, you can configure the health check for these as shown in the example
below.

```
external-dependency-health-checks:
health-endpoints:
maskinporten:
name: maskinporten
base-uri: [${maskinporten.oauth2.issuer}]()
endpoint: /health
connect-timeout-ms: 2000
read-timeout-ms: 1000
other-api:
name: otherApi
base-uri: http://example.com
endpoint: /health
connect-timeout-ms: 2000
read-timeout-ms: 1000
map-down-status-to: DOWN
```

To avoid any unintended consequences, the health indicators will return UP, DEGRADED or EXTERNAL_DEPENDENCY_DOWN pr
default. You can configure something other that EXTERNAL_DEPENDENCY_DOWN by setting the down-status property in the
health endpoint config. All of which are 200 OK responses. Overriding the http status code can be done in the default
spring management settings, for example:

```
management:
endpoint:
health:
status:
http-mapping:
DEGRADED: 200
EXTERNAL_DEPENDENCY_DOWN: 503
```

If you want to use the Custom health statuses, it is important to add them to the status order depending on your
application's requirements:

```
management:
endpoint:
health:
status:
order: DOWN, OUT_OF_SERVICE, UNKNOWN, DEGRADED, EXTERNAL_DEPENDENCY_DOWN, UP
```
60 changes: 30 additions & 30 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,18 @@
<description>Library for ID-porten actuators</description>

<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-boot.version>2.7.5</spring-boot.version>
<micrometer.prometheus.version>1.10.3</micrometer.prometheus.version>
<spring-boot.version>2.7.7</spring-boot.version>
<lombok.version>1.18.24</lombok.version>
<jackson-annotations.version>2.14.1</jackson-annotations.version>
<micrometer.version>1.10.3</micrometer.version>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version>
<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
<surefire.plugin.version>2.22.2</surefire.plugin.version>
<allure.cmd.download.url>
https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline
</allure.cmd.download.url>
<allure.version>2.20.1</allure.version>
<allure.report.version>2.4.1</allure.report.version>
<allure.maven.version>2.12.0</allure.maven.version>
</properties>

<dependencies>
Expand All @@ -38,36 +34,49 @@
<artifactId>log4j-to-slf4j</artifactId>
</exclusion>
</exclusions>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${micrometer.prometheus.version}</version>
<version>${micrometer.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<version>${allure.version}</version>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin> <!-- attach source package -->
<groupId>org.apache.maven.plugins</groupId>
Expand All @@ -93,18 +102,9 @@
<name>junit.jupiter.extensions.autodetection.enabled</name>
<value>true</value>
</property>
<allure.results.directory>${project.build.directory}/allure-results</allure.results.directory>
</systemProperties>
</configuration>
</plugin>
<plugin>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-maven</artifactId>
<version>${allure.maven.version}</version>
<configuration>
<reportVersion>${allure.report.version}</reportVersion>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package no.idporten.actuator.config;

import no.idporten.actuator.monitor.ExternalDependencyHealthIndicator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;

import java.time.Duration;


@Configuration
@EnableConfigurationProperties(HealthCheckEndpointProperties.class)
public class HealthCheckEndpointConfig implements InitializingBean {
private final Logger log = LoggerFactory.getLogger(HealthCheckEndpointConfig.class);
private final HealthCheckEndpointProperties healthCheckEndpointProperties;

public HealthCheckEndpointConfig(GenericApplicationContext applicationContext, HealthCheckEndpointProperties healthCheckEndpointProperties) {
this.healthCheckEndpointProperties = healthCheckEndpointProperties;
if (healthCheckEndpointProperties.healthEndpoints() != null) {
healthCheckEndpointProperties.healthEndpoints().values()
.forEach(healthCheckEndpoint -> {
log.info("Registering health check for {}", healthCheckEndpoint.name());
applicationContext.registerBean(healthCheckEndpoint.name() + "HealthCheck", ExternalDependencyHealthIndicator.class, () -> {
RestTemplateBuilder builder = new RestTemplateBuilder();
RestTemplate restTemplate = builder.rootUri(healthCheckEndpoint.baseUri())
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.setConnectTimeout(Duration.ofMillis(healthCheckEndpoint.connectTimeoutMs()))
.setReadTimeout(Duration.ofMillis(healthCheckEndpoint.readTimeoutMs()))
.build();
return new ExternalDependencyHealthIndicator(restTemplate, healthCheckEndpoint);
});
});
}
}

@Override
public void afterPropertiesSet() {
if (healthCheckEndpointProperties.healthEndpoints() == null || healthCheckEndpointProperties.healthEndpoints().isEmpty()) {
log.info("No health check endpoints configured");
} else {
log.info("Configured health check endpoints: {}", healthCheckEndpointProperties.healthEndpoints().keySet());
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package no.idporten.actuator.config;

import no.idporten.actuator.monitor.HealthCheckEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

import javax.validation.Valid;
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;

@Validated
@ConfigurationProperties(prefix = "external-dependency-health-checks")
public record HealthCheckEndpointProperties(
Map<String, @Valid HealthCheckEndpoint> healthEndpoints)
implements Serializable {

public Map<String, HealthCheckEndpoint> healthEndpoints() {
return healthEndpoints != null ? healthEndpoints : Collections.emptyMap();
}
}
13 changes: 13 additions & 0 deletions src/main/java/no/idporten/actuator/monitor/CustomStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package no.idporten.actuator.monitor;

import org.springframework.boot.actuate.health.Status;

import java.io.Serializable;

public class CustomStatus implements Serializable {
public static final Status DEGRADED = new Status("DEGRADED");
public static final Status EXTERNAL_DEPENDENCY_DOWN = new Status("EXTERNAL_DEPENDENCY_DOWN");

private CustomStatus() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package no.idporten.actuator.monitor;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

public class ExternalDependencyHealthIndicator implements HealthIndicator {

private final Logger log = LoggerFactory.getLogger(ExternalDependencyHealthIndicator.class);
private final RestTemplate restTemplate;
private final HealthCheckEndpoint healthCheckEndpoint;

public ExternalDependencyHealthIndicator(RestTemplate restTemplate, HealthCheckEndpoint healthCheckEndpoint) {
this.restTemplate = restTemplate;
this.healthCheckEndpoint = healthCheckEndpoint;
}

@Override
public Health health() {
try {
HealthResponse health = this.getIntegrationHealth();
if (Status.UP.getCode().equals(health.status())) {
return Health.up().build();
} else if (CustomStatus.DEGRADED.getCode().equals(health.status())) {
return Health.status(CustomStatus.DEGRADED).build();
} else {
return Health.status(healthCheckEndpoint.downStatus()).build();
}
} catch (HttpClientErrorException e) {
log.error("Health check to {} failed with exception {}", healthCheckEndpoint.name(), e.getMessage(), e);
if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
log.error("Wrong configuration of {} health endpoint?", healthCheckEndpoint.name());
}
return Health.status(healthCheckEndpoint.downStatus()).withException(e).build();
} catch (Exception e) {
log.error("Health check to {} failed with exception {}", healthCheckEndpoint.name(), e.getMessage(), e);
return Health.status(healthCheckEndpoint.downStatus()).withException(e).build();
}
}

protected HealthResponse getIntegrationHealth() {
ResponseEntity<HealthResponse> forEntity = this.restTemplate.getForEntity(healthCheckEndpoint.endpoint(),
HealthResponse.class);
if (forEntity.getBody() != null) {
return forEntity.getBody();
} else {
return new HealthResponse(Status.DOWN.getCode());
}
}

public String getName() {
return this.healthCheckEndpoint.name();
}

}
Loading

0 comments on commit 76e8529

Please sign in to comment.