Skip to content

CAMEL-20371: Asynchrnous Camel health checks #1075

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

Merged
merged 1 commit into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions core/camel-spring-boot/src/main/docs/spring-boot.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,24 @@ or by providing GraalVM JSON hint files that can be generated by the https://doc

For more details about `GraalVM Native Image Support` in Spring Boot please refer to https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html

== Camel Asynchronous Health Checks

Camel health checks can be executed asynchronously via a Task Scheduler so that the result can be cached and the actual health check is executed in background every few seconds. Asynchronous Camel health checks are disabled by default but can be enabled with the following property:

[source,properties]
----
camel.health.async-camel-health-check=true
----

moreover the Camel health check task scheduler can be customized with the following properties:

[source,properties]
----
camel.health.healthCheckPoolSize=5
camel.health.healthCheckFrequency=10
camel.health.healthCheckThreadNamePrefix=CamelHealthTaskScheduler
----

== Camel Readiness and Liveness State Indicators

Camel specific Readiness and Liveness checks can be added to a Spring Boot 3 application including respectively in the
Expand Down
28 changes: 28 additions & 0 deletions core/camel-spring-boot/src/main/docs/spring-boot.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,13 @@
"sourceType": "org.apache.camel.spring.boot.debug.CamelDebugConfigurationProperties",
"defaultValue": false
},
{
"name": "camel.health.async-camel-health-check",
"type": "java.lang.Boolean",
"description": "Whether Camel Health Checks are executed asynchronously <p> disabled by default",
"sourceType": "org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
"defaultValue": false
},
{
"name": "camel.health.consumers-enabled",
"type": "java.lang.Boolean",
Expand All @@ -538,6 +545,27 @@
"sourceType": "org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
"defaultValue": "default"
},
{
"name": "camel.health.health-check-frequency",
"type": "java.lang.Integer",
"description": "Camel's HealthCheck frequency in seconds",
"sourceType": "org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
"defaultValue": 10
},
{
"name": "camel.health.health-check-pool-size",
"type": "java.lang.Integer",
"description": "Camel HealthCheck pool size",
"sourceType": "org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
"defaultValue": 5
},
{
"name": "camel.health.health-check-thread-name-prefix",
"type": "java.lang.String",
"description": "Camel HealthCheck thread name prefix",
"sourceType": "org.apache.camel.spring.boot.actuate.health.CamelHealthCheckConfigurationProperties",
"defaultValue": "CamelHealthTaskScheduler"
},
{
"name": "camel.health.initial-state",
"type": "java.lang.String",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package org.apache.camel.spring.boot.actuate.health;

import org.apache.camel.spring.boot.actuate.health.liveness.CamelLivenessStateHealthIndicator;
import org.apache.camel.spring.boot.actuate.health.readiness.CamelReadinessStateHealthIndicator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.NamedContributor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import java.time.Duration;
import java.time.ZonedDateTime;

/**
* Configuration class that replace synchronous Camel Health Checks with asynchronous ones.
*
* This implementation is based on https://github.com/spring-projects/spring-boot/issues/2652 that most probably
* will be added in spring boot 3.2.x as a new feature in the future.
*
* TODO: To be refactored once async health contributors feature will be added in spring boot.
*
*/
@Configuration
@ConditionalOnProperty(prefix = "camel.health", name = "async-camel-health-check", havingValue = "true")
public class AsyncHealthIndicatorAutoConfiguration implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(AsyncHealthIndicatorAutoConfiguration.class);

private HealthContributorRegistry healthContributorRegistry;
private TaskScheduler taskScheduler;
private CamelHealthCheckConfigurationProperties config;

public AsyncHealthIndicatorAutoConfiguration(HealthContributorRegistry healthContributorRegistry,
CamelHealthCheckConfigurationProperties config) {
this.healthContributorRegistry = healthContributorRegistry;
this.config = config;

ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(config.getHealthCheckPoolSize());
threadPoolTaskScheduler.setThreadNamePrefix(config.getHealthCheckThreadNamePrefix());
threadPoolTaskScheduler.initialize();
taskScheduler = threadPoolTaskScheduler;
}

@Override
public void afterPropertiesSet() throws Exception {
for (NamedContributor<?> namedContributor : healthContributorRegistry) {
final String name = namedContributor.getName();
final Object contributor = namedContributor.getContributor();
if (contributor instanceof CamelHealthCheckIndicator
|| contributor instanceof CamelLivenessStateHealthIndicator
|| contributor instanceof CamelReadinessStateHealthIndicator) {
HealthIndicator camelHealthCheckIndicator = (HealthIndicator) contributor;
healthContributorRegistry.unregisterContributor(name);
log.debug(
"Wrapping " + contributor.getClass().getSimpleName() + " for async health scheduling");
WrappedHealthIndicator wrappedHealthIndicator =
new WrappedHealthIndicator(camelHealthCheckIndicator);
healthContributorRegistry.registerContributor(name, wrappedHealthIndicator);
taskScheduler.scheduleWithFixedDelay(
wrappedHealthIndicator, Duration.ofSeconds(config.getHealthCheckFrequency()));
}
}
}

/**
* Health Check Indicator that executes Health Checks within a Task Scheduler
*/
private static class WrappedHealthIndicator implements HealthIndicator, Runnable {
private static final String LAST_CHECKED_KEY = "lastChecked";
private static final String LAST_DURATION_KEY = "lastDuration";

private HealthIndicator wrappedHealthIndicator;

private Health lastHealth;

public WrappedHealthIndicator(HealthIndicator wrappedHealthIndicator) {
this.wrappedHealthIndicator = wrappedHealthIndicator;
}

@Override
public Health health() {
Health lastHealth = getLastHealth();
if (lastHealth == null) {
setLastHealth(getAndWrapHealth());
lastHealth = getLastHealth();
}

return lastHealth;
}

private Health getAndWrapHealth() {
ZonedDateTime startTime = ZonedDateTime.now();
Health baseHealth = getWrappedHealthIndicator().health();
ZonedDateTime endTime = ZonedDateTime.now();
Duration duration = Duration.between(startTime, endTime);
return Health.status(baseHealth.getStatus())
.withDetails(baseHealth.getDetails())
.withDetail(LAST_CHECKED_KEY, startTime)
.withDetail(LAST_DURATION_KEY, duration)
.build();
}

@Override
public void run() {
setLastHealth(getAndWrapHealth());
}

public HealthIndicator getWrappedHealthIndicator() {
return wrappedHealthIndicator;
}

public Health getLastHealth() {
return lastHealth;
}

public void setLastHealth(Health lastHealth) {
this.lastHealth = lastHealth;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,28 @@ public class CamelHealthCheckConfigurationProperties {
*/
private String excludePattern;

/**
* Camel's HealthCheck frequency in seconds
*/
private int healthCheckFrequency = 10;

/**
* Camel HealthCheck pool size
*/
private int healthCheckPoolSize = 5;

/**
* Camel HealthCheck thread name prefix
*/
private String healthCheckThreadNamePrefix = "CamelHealthTaskScheduler";

/**
* Whether Camel Health Checks are executed asynchronously
* <p>
* disabled by default
*/
private boolean asyncCamelHealthCheck = false;

/**
* Sets the level of details to exposure as result of invoking health checks. There are the following levels: full,
* default, oneline
Expand Down Expand Up @@ -158,6 +180,38 @@ public String getInitialState() {
public void setInitialState(String initialState) {
this.initialState = initialState;
}

public int getHealthCheckFrequency() {
return healthCheckFrequency;
}

public void setHealthCheckFrequency(int healthCheckFrequency) {
this.healthCheckFrequency = healthCheckFrequency;
}

public int getHealthCheckPoolSize() {
return healthCheckPoolSize;
}

public void setHealthCheckPoolSize(int healthCheckPoolSize) {
this.healthCheckPoolSize = healthCheckPoolSize;
}

public String getHealthCheckThreadNamePrefix() {
return healthCheckThreadNamePrefix;
}

public void setHealthCheckThreadNamePrefix(String healthCheckThreadNamePrefix) {
this.healthCheckThreadNamePrefix = healthCheckThreadNamePrefix;
}

public boolean isAsyncCamelHealthCheck() {
return asyncCamelHealthCheck;
}

public void setAsyncCamelHealthCheck(boolean asyncCamelHealthCheck) {
this.asyncCamelHealthCheck = asyncCamelHealthCheck;
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ org.apache.camel.spring.boot.CamelAutoConfiguration
org.apache.camel.spring.boot.actuate.console.CamelDevConsoleAutoConfiguration
org.apache.camel.spring.boot.actuate.endpoint.CamelRouteControllerEndpointAutoConfiguration
org.apache.camel.spring.boot.actuate.endpoint.CamelRoutesEndpointAutoConfiguration
org.apache.camel.spring.boot.actuate.health.AsyncHealthIndicatorAutoConfiguration
org.apache.camel.spring.boot.actuate.health.CamelHealthCheckAutoConfiguration
org.apache.camel.spring.boot.actuate.health.CamelAvailabilityCheckAutoConfiguration
org.apache.camel.spring.boot.actuate.info.CamelInfoAutoConfiguration
Expand Down