You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by fm...@apache.org on 2024/01/26 15:53:41 UTC

(camel-spring-boot) branch main updated: CAMEL-20371: Asynchrnous Camel health checks

This is an automated email from the ASF dual-hosted git repository.

fmariani pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git


The following commit(s) were added to refs/heads/main by this push:
     new 583e137550a CAMEL-20371: Asynchrnous Camel health checks
583e137550a is described below

commit 583e137550a75fa65ee552cdec2358b112bd6c73
Author: Croway <fe...@gmail.com>
AuthorDate: Fri Jan 26 16:32:16 2024 +0100

    CAMEL-20371: Asynchrnous Camel health checks
---
 .../src/main/docs/spring-boot.adoc                 |  18 +++
 .../src/main/docs/spring-boot.json                 |  28 +++++
 .../AsyncHealthIndicatorAutoConfiguration.java     | 126 +++++++++++++++++++++
 .../CamelHealthCheckConfigurationProperties.java   |  54 +++++++++
 ...rk.boot.autoconfigure.AutoConfiguration.imports |   1 +
 5 files changed, 227 insertions(+)

diff --git a/core/camel-spring-boot/src/main/docs/spring-boot.adoc b/core/camel-spring-boot/src/main/docs/spring-boot.adoc
index e29685efea7..684bbc10ca9 100644
--- a/core/camel-spring-boot/src/main/docs/spring-boot.adoc
+++ b/core/camel-spring-boot/src/main/docs/spring-boot.adoc
@@ -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
diff --git a/core/camel-spring-boot/src/main/docs/spring-boot.json b/core/camel-spring-boot/src/main/docs/spring-boot.json
index 878e51ec9a0..8ddf6bb8106 100644
--- a/core/camel-spring-boot/src/main/docs/spring-boot.json
+++ b/core/camel-spring-boot/src/main/docs/spring-boot.json
@@ -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",
@@ -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",
diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/AsyncHealthIndicatorAutoConfiguration.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/AsyncHealthIndicatorAutoConfiguration.java
new file mode 100644
index 00000000000..0a58d9e3b3c
--- /dev/null
+++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/AsyncHealthIndicatorAutoConfiguration.java
@@ -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;
+		}
+	}
+}
diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java
index d4c7f6c47c6..fd45283a36b 100644
--- a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java
+++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/health/CamelHealthCheckConfigurationProperties.java
@@ -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
@@ -158,6 +180,38 @@ public class CamelHealthCheckConfigurationProperties {
     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;
+    }
 }
 
 
diff --git a/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 13a8efb52ca..cf103fe6f22 100644
--- a/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/core/camel-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -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