diff --git a/components/camel-microprofile/camel-microprofile-health/pom.xml b/components/camel-microprofile/camel-microprofile-health/pom.xml index 846b06e7a3cb6..07c97833db8f0 100644 --- a/components/camel-microprofile/camel-microprofile-health/pom.xml +++ b/components/camel-microprofile/camel-microprofile-health/pom.xml @@ -46,16 +46,22 @@ - org.eclipse.microprofile.health - microprofile-health-api - ${microprofile-health-version} - provided + io.smallrye + smallrye-health + ${smallrye-health-version} - + + io.smallrye.config + smallrye-config + ${smallrye-config-version} + + + javax.enterprise cdi-api ${cdi-api-2.0-version} + provided @@ -64,17 +70,10 @@ camel-test-junit5 test - - io.smallrye - smallrye-health - ${smallrye-health-version} - test - - - io.smallrye.config - smallrye-config - ${smallrye-config-version} + org.jboss.weld + weld-junit5 + ${weld-junit5-version} test @@ -104,4 +103,23 @@ + + + + jdk17-build + + [17,) + + + + + maven-surefire-plugin + + --add-opens java.base/java.lang=ALL-UNNAMED + + + + + + diff --git a/components/camel-microprofile/camel-microprofile-health/src/main/docs/microprofile-health.adoc b/components/camel-microprofile/camel-microprofile-health/src/main/docs/microprofile-health.adoc index aad6994610ffa..bd0c25c97a5d1 100644 --- a/components/camel-microprofile/camel-microprofile-health/src/main/docs/microprofile-health.adoc +++ b/components/camel-microprofile/camel-microprofile-health/src/main/docs/microprofile-health.adoc @@ -27,6 +27,13 @@ for this component: == Usage +This component provides a custom `HealthCheckRegistry` implementation that needs to be registered on the `CamelContext`. +[source,java] +---- +HealthCheckRegistry registry = new CamelMicroProfileHealthCheckRegistry(); +camelContext.setExtension(HealthCheckRegistry.class, registry); +---- + By default, Camel health checks are registered as both MicroProfile Health liveness and readiness checks. To have finer control over whether a Camel health check should be considered either a readiness or liveness check, you can extend `AbstractHealthCheck` and override the `isLiveness()` and `isReadiness()` methods. @@ -57,14 +64,3 @@ public class MyHealthCheck extends AbstractHealthCheck { } } ---- - -== Auto discovery - -The expectation is that this component is run within a MicroProfile container, where CDI and a library implementing the MicroProfile health specification is available. -E.g https://github.com/smallrye/smallrye-health[SmallRye Health]. - -In this scenario, the readiness and liveness checks are automatically discovered and registered for you. - -However, it's still possible to manually -register Health checks without CDI. Ensure your camel health checks are available in the Camel registry and add an instance of -`CamelMicroProfileReadinessCheck` and `CamelMicroProfileLivenessCheck` to the health check registry of your MicroProfile Health implementation. diff --git a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/AbstractCamelMicroProfileHealthCheck.java b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/AbstractCamelMicroProfileHealthCheck.java deleted file mode 100644 index d573d29cba117..0000000000000 --- a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/AbstractCamelMicroProfileHealthCheck.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.camel.microprofile.health; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Collection; -import java.util.Map; - -import javax.inject.Inject; - -import org.apache.camel.CamelContext; -import org.apache.camel.CamelContextAware; -import org.apache.camel.health.HealthCheck.Result; -import org.apache.camel.health.HealthCheck.State; -import org.apache.camel.health.HealthCheckFilter; -import org.apache.camel.health.HealthCheckHelper; -import org.apache.camel.impl.health.AbstractHealthCheck; -import org.eclipse.microprofile.health.HealthCheck; -import org.eclipse.microprofile.health.HealthCheckResponse; -import org.eclipse.microprofile.health.HealthCheckResponseBuilder; - -/** - * Invokes Camel health checks and adds their results into the HealthCheckResponseBuilder - */ -public abstract class AbstractCamelMicroProfileHealthCheck implements HealthCheck, CamelContextAware { - - @Inject - protected CamelContext camelContext; - - protected abstract boolean acceptHealthCheck(AbstractHealthCheck check); - - @Override - public HealthCheckResponse call() { - final HealthCheckResponseBuilder builder = HealthCheckResponse.builder(); - builder.name(getHealthCheckName()); - builder.up(); - - if (camelContext != null) { - Collection results = HealthCheckHelper.invoke(camelContext, - (HealthCheckFilter) check -> check instanceof AbstractHealthCheck - && !acceptHealthCheck((AbstractHealthCheck) check)); - - for (Result result : results) { - Map details = result.getDetails(); - boolean enabled = true; - - if (details.containsKey(AbstractHealthCheck.CHECK_ENABLED)) { - enabled = (boolean) details.get(AbstractHealthCheck.CHECK_ENABLED); - } - - if (enabled) { - details.forEach((key, value) -> builder.withData(key, value.toString())); - - result.getError().ifPresent(error -> { - builder.withData("error.message", error.getMessage()); - try (final StringWriter stackTraceWriter = new StringWriter(); - final PrintWriter pw = new PrintWriter(stackTraceWriter, true)) { - error.printStackTrace(pw); - builder.withData("error.stacktrace", stackTraceWriter.getBuffer().toString()); - } catch (IOException exception) { - // ignore - } - }); - - builder.withData(result.getCheck().getId(), result.getState().name()); - if (result.getState() == State.DOWN) { - builder.down(); - } - } - } - } - - return builder.build(); - } - - @Override - public CamelContext getCamelContext() { - return this.camelContext; - } - - @Override - public void setCamelContext(CamelContext camelContext) { - this.camelContext = camelContext; - } - - /** - * Whether this health check can be used for readiness checks - */ - public boolean isReadiness() { - return true; - } - - /** - * Whether this health check can be used for liveness checks - */ - public boolean isLiveness() { - return true; - } - - /** - * Gets the name of the health check which will be used as a heading for the associated checks. - * - * @return the health check name - */ - abstract String getHealthCheckName(); -} diff --git a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/AbstractCamelMicroProfileLivenessCheck.java b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/AbstractCamelMicroProfileLivenessCheck.java deleted file mode 100644 index 37c6388e3fed6..0000000000000 --- a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/AbstractCamelMicroProfileLivenessCheck.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.camel.microprofile.health; - -import java.util.Map; - -import org.apache.camel.impl.health.AbstractHealthCheck; - -/** - * Ensures the implemented health check will be considered as a MicroProfile Health liveness check - * - * @deprecated extend {@link AbstractHealthCheck} then override and return false from isReadiness - */ -@Deprecated -public abstract class AbstractCamelMicroProfileLivenessCheck extends AbstractHealthCheck { - - public AbstractCamelMicroProfileLivenessCheck(String id) { - super("camel", id); - } - - protected AbstractCamelMicroProfileLivenessCheck(String group, String id) { - super(group, id); - } - - protected AbstractCamelMicroProfileLivenessCheck(String group, String id, Map meta) { - super(group, id, meta); - } - - @Override - public boolean isReadiness() { - return false; - } - -} diff --git a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/AbstractCamelMicroProfileReadinessCheck.java b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/AbstractCamelMicroProfileReadinessCheck.java deleted file mode 100644 index 7971e10061cdc..0000000000000 --- a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/AbstractCamelMicroProfileReadinessCheck.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.camel.microprofile.health; - -import org.apache.camel.impl.health.AbstractHealthCheck; - -/** - * Ensures the implemented health check will be considered as a MicroProfile Health readiness check - * - * @deprecated extend {@link AbstractHealthCheck} then override and return false from isLiveness - */ -@Deprecated -public abstract class AbstractCamelMicroProfileReadinessCheck extends AbstractHealthCheck { - - public AbstractCamelMicroProfileReadinessCheck(String id) { - super("camel", id); - } - - public AbstractCamelMicroProfileReadinessCheck(String group, String id) { - super(group, id); - } - - @Override - public boolean isLiveness() { - return false; - } - -} diff --git a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheck.java b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheck.java new file mode 100644 index 0000000000000..efa22c97f089b --- /dev/null +++ b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheck.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.microprofile.health; + +import java.util.Map; + +import org.apache.camel.impl.health.AbstractHealthCheck; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; + +import static org.apache.camel.health.HealthCheck.*; + +/** + * A MicroProfile {@link HealthCheck} that invokes the supplied Camel health check, reports its health status and + * associated details. + */ +final class CamelMicroProfileHealthCheck implements HealthCheck { + + private final org.apache.camel.health.HealthCheck camelHealthCheck; + + CamelMicroProfileHealthCheck(org.apache.camel.health.HealthCheck camelHealthCheck) { + this.camelHealthCheck = camelHealthCheck; + } + + @Override + public HealthCheckResponse call() { + final HealthCheckResponseBuilder builder = HealthCheckResponse.builder(); + builder.name(camelHealthCheck.getId()); + builder.up(); + + Result result = camelHealthCheck.call(); + Map details = result.getDetails(); + boolean enabled = true; + + if (details.containsKey(AbstractHealthCheck.CHECK_ENABLED)) { + enabled = (boolean) details.get(AbstractHealthCheck.CHECK_ENABLED); + } + + if (enabled) { + CamelMicroProfileHealthHelper.applyHealthDetail(builder, result); + + if (result.getState() == State.DOWN) { + builder.down(); + } + } else { + builder.withData(AbstractHealthCheck.CHECK_ENABLED, false); + } + + return builder.build(); + } +} diff --git a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheckRegistry.java b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheckRegistry.java new file mode 100644 index 0000000000000..536d84319180a --- /dev/null +++ b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheckRegistry.java @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.microprofile.health; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import io.smallrye.health.api.HealthRegistry; +import org.apache.camel.CamelContext; +import org.apache.camel.StartupListener; +import org.apache.camel.health.HealthCheck; +import org.apache.camel.health.HealthCheckRegistry; +import org.apache.camel.health.HealthCheckRepository; +import org.apache.camel.impl.health.ConsumersHealthCheckRepository; +import org.apache.camel.impl.health.DefaultHealthCheckRegistry; +import org.apache.camel.impl.health.RoutesHealthCheckRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link HealthCheckRegistry} implementation to register Camel health checks as MicroProfile health checks on SmallRye + * Health. + */ +public class CamelMicroProfileHealthCheckRegistry extends DefaultHealthCheckRegistry implements StartupListener { + + public static final String CONSUMERS_CHECK_NAME = "camel-consumers"; + public static final String ROUTES_CHECK_NAME = "camel-routes"; + private static final Logger LOG = LoggerFactory.getLogger(CamelMicroProfileHealthCheckRegistry.class); + private final Set repositories = new CopyOnWriteArraySet<>(); + private HealthRegistry livenessRegistry; + private HealthRegistry readinessRegistry; + + public CamelMicroProfileHealthCheckRegistry() { + this(null); + } + + public CamelMicroProfileHealthCheckRegistry(CamelContext camelContext) { + super(camelContext); + super.setId("camel-microprofile-health"); + } + + @Override + protected void doInit() throws Exception { + super.doInit(); + super.getCamelContext().addStartupListener(this); + } + + @Override + public boolean register(Object obj) { + boolean registered = super.register(obj); + if (obj instanceof HealthCheck) { + HealthCheck check = (HealthCheck) obj; + if (check.getConfiguration().isEnabled()) { + registerMicroProfileHealthCheck(check); + } + } else { + HealthCheckRepository repository = (HealthCheckRepository) obj; + if (repository.stream().findAny().isPresent()) { + registerRepositoryChecks(repository); + } else { + // Try health check registration again on CamelContext started + repositories.add(repository); + } + } + return registered; + } + + @Override + public boolean unregister(Object obj) { + boolean unregistered = super.unregister(obj); + if (obj instanceof HealthCheck) { + HealthCheck check = (HealthCheck) obj; + removeMicroProfileHealthCheck(check); + } else { + HealthCheckRepository repository = (HealthCheckRepository) obj; + if (repository instanceof ConsumersHealthCheckRepository || repository instanceof RoutesHealthCheckRepository) { + try { + getReadinessRegistry().remove(repository.getId()); + } catch (IllegalStateException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Failed to remove repository readiness health {} check due to: {}", repository.getId(), + e.getMessage()); + } + } + } else { + repository.stream().forEach(this::removeMicroProfileHealthCheck); + } + } + return unregistered; + } + + @Override + public void onCamelContextStarted(CamelContext context, boolean alreadyStarted) throws Exception { + //Noop + } + + @Override + public void onCamelContextFullyStarted(CamelContext context, boolean alreadyStarted) throws Exception { + // Some repository checks may not be resolvable earlier in the lifecycle, so try one last time on CamelContext started + if (alreadyStarted) { + repositories.stream() + .filter(repository -> repository.stream().findAny().isPresent()) + .forEach(this::registerRepositoryChecks); + repositories.clear(); + } + } + + private void registerRepositoryChecks(HealthCheckRepository repository) { + if (repository.isEnabled()) { + // Since the number of potential checks for consumers / routes is non-deterministic + // avoid registering each one with SmallRye health and instead aggregate the results so + // that we avoid highly verbose health output + if (repository instanceof ConsumersHealthCheckRepository) { + CamelMicroProfileRepositoryHealthCheck repositoryHealthCheck + = new CamelMicroProfileRepositoryHealthCheck(repository, CONSUMERS_CHECK_NAME); + getReadinessRegistry().register(repository.getId(), repositoryHealthCheck); + } else if (repository instanceof RoutesHealthCheckRepository) { + CamelMicroProfileRepositoryHealthCheck repositoryHealthCheck + = new CamelMicroProfileRepositoryHealthCheck(repository, ROUTES_CHECK_NAME); + getReadinessRegistry().register(repository.getId(), repositoryHealthCheck); + } else { + repository.stream() + .filter(healthCheck -> healthCheck.getConfiguration().isEnabled()) + .forEach(this::registerMicroProfileHealthCheck); + } + } + } + + private void registerMicroProfileHealthCheck(HealthCheck camelHealthCheck) { + org.eclipse.microprofile.health.HealthCheck microProfileHealthCheck + = new CamelMicroProfileHealthCheck(camelHealthCheck); + + if (camelHealthCheck.isReadiness()) { + getReadinessRegistry().register(camelHealthCheck.getId(), microProfileHealthCheck); + } + + if (camelHealthCheck.isLiveness()) { + getLivenessRegistry().register(camelHealthCheck.getId(), microProfileHealthCheck); + } + } + + private void removeMicroProfileHealthCheck(HealthCheck camelHealthCheck) { + if (camelHealthCheck.isReadiness()) { + try { + getReadinessRegistry().remove(camelHealthCheck.getId()); + } catch (IllegalStateException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Failed to remove readiness health check due to: {}", e.getMessage()); + } + } + } + + if (camelHealthCheck.isLiveness()) { + try { + getLivenessRegistry().remove(camelHealthCheck.getId()); + } catch (IllegalStateException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Failed to remove liveness health check due to: {}", e.getMessage()); + } + } + } + } + + private HealthRegistry getLivenessRegistry() { + synchronized (this) { + if (livenessRegistry == null) { + livenessRegistry = CamelMicroProfileHealthHelper.getLivenessRegistry(); + } + } + return livenessRegistry; + } + + private HealthRegistry getReadinessRegistry() { + synchronized (this) { + if (readinessRegistry == null) { + readinessRegistry = CamelMicroProfileHealthHelper.getReadinessRegistry(); + } + } + return readinessRegistry; + } +} diff --git a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthHelper.java b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthHelper.java new file mode 100644 index 0000000000000..ecd9fffb1e56c --- /dev/null +++ b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthHelper.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.microprofile.health; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.annotation.Annotation; +import java.util.Set; + +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.CDI; + +import io.smallrye.health.api.HealthRegistry; +import io.smallrye.health.registry.LivenessHealthRegistry; +import io.smallrye.health.registry.ReadinessHealthRegistry; +import org.apache.camel.health.HealthCheck; +import org.apache.camel.health.HealthCheck.Result; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.health.Liveness; +import org.eclipse.microprofile.health.Readiness; + +/** + * Helper utility class for MicroProfile health checks. + */ +final class CamelMicroProfileHealthHelper { + + private CamelMicroProfileHealthHelper() { + // Utility class + } + + /** + * Propagates details from the Camel Health {@link Result} to the MicroProfile {@link HealthCheckResponseBuilder}. + * + * @param builder The health check response builder + * @param result The Camel health check result + */ + public static void applyHealthDetail(HealthCheckResponseBuilder builder, Result result) { + HealthCheck check = result.getCheck(); + Set metaKeys = check.getMetaData().keySet(); + + result.getDetails().forEach((key, value) -> { + // Filter health check metadata to have a less verbose output + if (!metaKeys.contains(key)) { + builder.withData(key, value.toString()); + } + }); + + result.getError().ifPresent(error -> { + builder.withData("error.message", error.getMessage()); + + final StringWriter stackTraceWriter = new StringWriter(); + try (final PrintWriter pw = new PrintWriter(stackTraceWriter, true)) { + error.printStackTrace(pw); + builder.withData("error.stacktrace", stackTraceWriter.toString()); + } + }); + } + + /** + * Retrieves the {@link LivenessHealthRegistry} bean instance. + * + * @return The {@link LivenessHealthRegistry} bean. + */ + public static HealthRegistry getLivenessRegistry() { + return getHealthRegistryBean(LivenessHealthRegistry.class, Liveness.Literal.INSTANCE); + } + + /** + * Retrieves the {@link ReadinessHealthRegistry} bean instance. + * + * @return The {@link ReadinessHealthRegistry} bean. + */ + public static HealthRegistry getReadinessRegistry() { + return getHealthRegistryBean(ReadinessHealthRegistry.class, Readiness.Literal.INSTANCE); + } + + /** + * Retrieves a {@link HealthRegistry} bean from the CDI bean manager for the given type and qualifier. + * + * Registry beans are looked up from the CDI {@link BeanManager} to avoid CDI injection in + * {@link CamelMicroProfileHealthCheckRegistry} and also avoid having to add CDI bean defining annotations to + * {@link CamelMicroProfileHealthCheckRegistry}. + * + * Eventually this can be removed when upgrading to a future SmallRye Health release where static health registry + * lookups will be supported. + * + * https://github.com/smallrye/smallrye-health/issues/172 + * + * @param type The implementation class of the {@link HealthRegistry} bean + * @param qualifier The annotation qualifier applied to the {@link HealthRegistry} bean + * @return The {@link HealthRegistry} bean + * @throws IllegalStateException if no beans matching the {@link HealthRegistry} bean type and annotation qualifier + * were found + */ + private static HealthRegistry getHealthRegistryBean(Class type, Annotation qualifier) { + BeanManager beanManager = CDI.current().getBeanManager(); + Set> beans = beanManager.getBeans(type, qualifier); + if (beans.isEmpty()) { + throw new IllegalStateException( + "Beans for type " + type.getName() + " with qualifier " + qualifier + " could not be found."); + } + + Bean bean = beanManager.resolve(beans); + Object reference = beanManager.getReference(bean, type, beanManager.createCreationalContext(bean)); + return type.cast(reference); + } +} diff --git a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileLivenessCheck.java b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileLivenessCheck.java deleted file mode 100644 index 61ef1c4697390..0000000000000 --- a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileLivenessCheck.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.camel.microprofile.health; - -import org.apache.camel.impl.health.AbstractHealthCheck; -import org.eclipse.microprofile.health.Liveness; - -/** - * Liveness checks - */ -@Liveness -public class CamelMicroProfileLivenessCheck extends AbstractCamelMicroProfileHealthCheck { - - @Override - public boolean isReadiness() { - return false; - } - - protected boolean acceptHealthCheck(AbstractHealthCheck check) { - return check.isLiveness(); - } - - @Override - String getHealthCheckName() { - return "camel-liveness-checks"; - } -} diff --git a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileReadinessCheck.java b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileReadinessCheck.java deleted file mode 100644 index 0ff301ca10b68..0000000000000 --- a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileReadinessCheck.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.camel.microprofile.health; - -import org.apache.camel.impl.health.AbstractHealthCheck; -import org.eclipse.microprofile.health.Readiness; - -/** - * Readiness checks - */ -@Readiness -public class CamelMicroProfileReadinessCheck extends AbstractCamelMicroProfileHealthCheck { - - @Override - public boolean isLiveness() { - return false; - } - - protected boolean acceptHealthCheck(AbstractHealthCheck check) { - return check.isReadiness(); - } - - @Override - String getHealthCheckName() { - return "camel-readiness-checks"; - } -} diff --git a/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileRepositoryHealthCheck.java b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileRepositoryHealthCheck.java new file mode 100644 index 0000000000000..4cc853087bcd5 --- /dev/null +++ b/components/camel-microprofile/camel-microprofile-health/src/main/java/org/apache/camel/microprofile/health/CamelMicroProfileRepositoryHealthCheck.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.microprofile.health; + +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.camel.health.HealthCheck.Result; +import org.apache.camel.health.HealthCheck.State; +import org.apache.camel.health.HealthCheckRepository; +import org.apache.camel.impl.health.AbstractHealthCheck; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; + +/** + * Invokes health checks registered with a {@link HealthCheckRepository} and resolves / aggregates the results into a + * single UP / DOWN status. + */ +final class CamelMicroProfileRepositoryHealthCheck implements HealthCheck { + + private final HealthCheckRepository repository; + private final String name; + + CamelMicroProfileRepositoryHealthCheck(HealthCheckRepository repository, String name) { + this.repository = repository; + this.name = name; + } + + @Override + public HealthCheckResponse call() { + final HealthCheckResponseBuilder builder = HealthCheckResponse.builder(); + builder.name(name); + builder.up(); + + if (repository.isEnabled()) { + List results = repository.stream() + .filter(healthCheck -> healthCheck.getConfiguration().isEnabled()) + .map(org.apache.camel.health.HealthCheck::call) + .filter(result -> result != null) + .collect(Collectors.toList()); + + // If any of the result statuses is DOWN, find the first one and report any error details + results.stream() + .filter(result -> result.getState().equals(State.DOWN)) + .findFirst() + .ifPresent(result -> { + CamelMicroProfileHealthHelper.applyHealthDetail(builder, result); + builder.down(); + }); + } else { + builder.withData(AbstractHealthCheck.CHECK_ENABLED, false); + } + + return builder.build(); + } +} diff --git a/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheckRepositoryTest.java b/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheckRepositoryTest.java index 83d1be847839f..33e9ad30b4ca0 100644 --- a/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheckRepositoryTest.java +++ b/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheckRepositoryTest.java @@ -16,17 +16,27 @@ */ package org.apache.camel.microprofile.health; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + import javax.json.JsonArray; import javax.json.JsonObject; import io.smallrye.health.SmallRyeHealth; import org.apache.camel.RoutesBuilder; +import org.apache.camel.ServiceStatus; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.health.HealthCheck; +import org.apache.camel.health.HealthCheckConfiguration; import org.apache.camel.health.HealthCheckRegistry; +import org.apache.camel.health.HealthCheckRepository; import org.eclipse.microprofile.health.HealthCheckResponse.Status; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import static org.apache.camel.microprofile.health.CamelMicroProfileHealthCheckRegistry.ROUTES_CHECK_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; public class CamelMicroProfileHealthCheckRepositoryTest extends CamelMicroProfileHealthTestSupport { @@ -38,10 +48,6 @@ public void testCamelHealthRepositoryUpStatus() { Object hc = healthCheckRegistry.resolveById("routes"); healthCheckRegistry.register(hc); - CamelMicroProfileReadinessCheck readinessCheck = new CamelMicroProfileReadinessCheck(); - readinessCheck.setCamelContext(context); - reporter.addHealthCheck(readinessCheck); - SmallRyeHealth health = reporter.getHealth(); JsonObject healthObject = getHealthJson(health); @@ -50,13 +56,7 @@ public void testCamelHealthRepositoryUpStatus() { JsonArray checks = healthObject.getJsonArray("checks"); assertEquals(1, checks.size()); - Assertions.assertNotNull(checks.getJsonObject(0).getJsonObject("data")); - Assertions.assertEquals("healthyRoute", checks.getJsonObject(0).getJsonObject("data").getString("route.id")); - - assertHealthCheckOutput("camel-readiness-checks", Status.UP, checks.getJsonObject(0), jsonObject -> { - assertEquals(Status.UP.name(), jsonObject.getString("route:healthyRoute")); - assertEquals("Started", jsonObject.getString("route.status")); - }); + assertHealthCheckOutput(ROUTES_CHECK_NAME, Status.UP, checks.getJsonObject(0)); } @Test @@ -66,10 +66,6 @@ public void testCamelHealthRepositoryDownStatus() throws Exception { Object hc = healthCheckRegistry.resolveById("routes"); healthCheckRegistry.register(hc); - CamelMicroProfileReadinessCheck readinessCheck = new CamelMicroProfileReadinessCheck(); - readinessCheck.setCamelContext(context); - reporter.addHealthCheck(readinessCheck); - context.getRouteController().stopRoute("healthyRoute"); SmallRyeHealth health = reporter.getHealth(); @@ -80,12 +76,118 @@ public void testCamelHealthRepositoryDownStatus() throws Exception { JsonArray checks = healthObject.getJsonArray("checks"); assertEquals(1, checks.size()); - assertHealthCheckOutput("camel-readiness-checks", Status.DOWN, checks.getJsonObject(0), jsonObject -> { - assertEquals(Status.DOWN.name(), jsonObject.getString("route:healthyRoute")); - assertEquals("Stopped", jsonObject.getString("route.status")); + assertHealthCheckOutput(ROUTES_CHECK_NAME, Status.DOWN, checks.getJsonObject(0), jsonObject -> { + assertEquals("healthyRoute", jsonObject.getString("route.id")); + assertEquals(ServiceStatus.Stopped.name(), jsonObject.getString("route.status")); }); } + @Test + public void testCamelHealthCheckRepositoryDisabled() throws Exception { + HealthCheckRegistry healthCheckRegistry = HealthCheckRegistry.get(context); + // register disabled routes health check repository + HealthCheckRepository hc = (HealthCheckRepository) healthCheckRegistry.resolveById("routes"); + hc.setEnabled(false); + healthCheckRegistry.register(hc); + + context.getRouteController().stopRoute("healthyRoute"); + + SmallRyeHealth health = reporter.getHealth(); + + JsonObject healthObject = getHealthJson(health); + assertEquals(Status.UP.name(), healthObject.getString("status")); + + JsonArray checks = healthObject.getJsonArray("checks"); + assertEquals(0, checks.size()); + } + + @Test + public void testCamelHealthCheckRepositorySpecificChecksDisabled() throws Exception { + List repositoryChecks = new ArrayList<>(); + repositoryChecks.add(createLivenessCheck("check-1", true, builder -> builder.up())); + repositoryChecks.add(createLivenessCheck("check-2", false, builder -> builder.up())); + repositoryChecks.add(createLivenessCheck("check-3", true, builder -> builder.down())); + + HealthCheckRepository repository = new HealthCheckRepository() { + @Override + public boolean isEnabled() { + return true; + } + + @Override + public void setEnabled(boolean enabled) { + // Noop + } + + @Override + public void setConfigurations(Map configurations) { + // Noop + } + + @Override + public Map getConfigurations() { + return Collections.emptyMap(); + } + + @Override + public void addConfiguration(String id, HealthCheckConfiguration configuration) { + // Noop + } + + @Override + public Stream stream() { + return repositoryChecks.stream(); + } + + @Override + public String getId() { + return "custom-repository"; + } + }; + + HealthCheckRegistry healthCheckRegistry = HealthCheckRegistry.get(context); + healthCheckRegistry.register(repository); + + SmallRyeHealth health = reporter.getHealth(); + + JsonObject healthObject = getHealthJson(health); + assertEquals(Status.DOWN.name(), healthObject.getString("status")); + + JsonArray checks = healthObject.getJsonArray("checks"); + assertEquals(2, checks.size()); + + assertHealthCheckOutput("check-3", Status.DOWN, checks.getJsonObject(0)); + + assertHealthCheckOutput("check-1", Status.UP, checks.getJsonObject(1)); + } + + @Test + public void testRoutesRepositoryUnregister() { + HealthCheckRegistry healthCheckRegistry = HealthCheckRegistry.get(context); + Object hc = healthCheckRegistry.resolveById("routes"); + healthCheckRegistry.register(hc); + + SmallRyeHealth health = reporter.getHealth(); + + JsonObject healthObject = getHealthJson(health); + assertEquals(Status.UP.name(), healthObject.getString("status")); + + JsonArray checks = healthObject.getJsonArray("checks"); + assertEquals(1, checks.size()); + + assertHealthCheckOutput(ROUTES_CHECK_NAME, Status.UP, checks.getJsonObject(0)); + + healthCheckRegistry.unregister(hc); + + health = reporter.getHealth(); + + healthObject = getHealthJson(health); + assertEquals(Status.UP.name(), healthObject.getString("status")); + + checks = healthObject.getJsonArray("checks"); + assertEquals(0, checks.size()); + } + @Override protected RoutesBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { diff --git a/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheckTest.java b/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheckTest.java index c040e77706714..298c842a8eefe 100644 --- a/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheckTest.java +++ b/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthCheckTest.java @@ -16,41 +16,44 @@ */ package org.apache.camel.microprofile.health; +import java.util.Map; + import javax.json.JsonArray; import javax.json.JsonObject; import io.smallrye.health.SmallRyeHealth; +import org.apache.camel.ServiceStatus; +import org.apache.camel.health.HealthCheck; import org.apache.camel.health.HealthCheckRegistry; +import org.apache.camel.health.HealthCheckResultBuilder; import org.apache.camel.impl.engine.ExplicitCamelContextNameStrategy; +import org.apache.camel.impl.health.AbstractHealthCheck; import org.apache.camel.impl.health.ContextHealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse.Status; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; public class CamelMicroProfileHealthCheckTest extends CamelMicroProfileHealthTestSupport { @Test - public void testCamelContextHealthCheckUpStatus() { + public void testCamelContextHealthCheckUpStatus() throws Exception { context.setNameStrategy(new ExplicitCamelContextNameStrategy("health-context")); context.getExtension(HealthCheckRegistry.class).register(new ContextHealthCheck()); - CamelMicroProfileReadinessCheck check = new CamelMicroProfileReadinessCheck(); - check.setCamelContext(context); - reporter.addHealthCheck(check); - SmallRyeHealth health = reporter.getHealth(); JsonObject healthObject = getHealthJson(health); - assertEquals(Status.UP.name(), healthObject.getString("status")); JsonArray checks = healthObject.getJsonArray("checks"); assertEquals(1, checks.size()); - assertHealthCheckOutput("camel-readiness-checks", Status.UP, checks.getJsonObject(0), checksJson -> { - assertEquals(Status.UP.name(), checksJson.getString("context")); + assertHealthCheckOutput("context", Status.UP, checks.getJsonObject(0), checksJson -> { + assertEquals("health-context", checksJson.getString("context.name")); + assertEquals(ServiceStatus.Started.name(), checksJson.getString("context.status")); }); } @@ -59,10 +62,6 @@ public void testCamelContextHealthCheckDownStatus() { context.setNameStrategy(new ExplicitCamelContextNameStrategy("health-context")); context.getExtension(HealthCheckRegistry.class).register(new ContextHealthCheck()); - CamelMicroProfileReadinessCheck check = new CamelMicroProfileReadinessCheck(); - check.setCamelContext(context); - reporter.addHealthCheck(check); - context.stop(); SmallRyeHealth health = reporter.getHealth(); @@ -73,8 +72,9 @@ public void testCamelContextHealthCheckDownStatus() { JsonArray checks = healthObject.getJsonArray("checks"); assertEquals(1, checks.size()); - assertHealthCheckOutput("camel-readiness-checks", Status.DOWN, checks.getJsonObject(0), checksJson -> { - assertEquals(Status.DOWN.name(), checksJson.getString("context")); + assertHealthCheckOutput("context", Status.DOWN, checks.getJsonObject(0), checksJson -> { + assertEquals("health-context", checksJson.getString("context.name")); + assertEquals(ServiceStatus.Stopped.name(), checksJson.getString("context.status")); }); } @@ -86,23 +86,16 @@ public void testCamelMicroProfileLivenessCheckUpStatus() { healthCheckRegistry.register(createLivenessCheck("liveness-2", true, builder -> builder.up())); healthCheckRegistry.register(createReadinessCheck("readiness-3", true, builder -> builder.up())); - CamelMicroProfileLivenessCheck livenessCheck = new CamelMicroProfileLivenessCheck(); - livenessCheck.setCamelContext(context); - reporter.addHealthCheck(livenessCheck); - - SmallRyeHealth health = reporter.getHealth(); + SmallRyeHealth health = reporter.getLiveness(); JsonObject healthObject = getHealthJson(health); assertEquals(Status.UP.name(), healthObject.getString("status")); JsonArray checks = healthObject.getJsonArray("checks"); - assertEquals(1, checks.size()); + assertEquals(2, checks.size()); - JsonObject checksObject = checks.getJsonObject(0); - assertHealthCheckOutput("camel-liveness-checks", Status.UP, checksObject, checksJson -> { - assertEquals(Status.UP.name(), checksJson.getString("liveness-1")); - assertEquals(Status.UP.name(), checksJson.getString("liveness-2")); - }); + assertHealthCheckOutput("liveness-1", Status.UP, checks.getJsonObject(0)); + assertHealthCheckOutput("liveness-2", Status.UP, checks.getJsonObject(1)); } @Test @@ -113,23 +106,16 @@ public void testCamelMicroProfileLivenessCheckDownStatus() { healthCheckRegistry.register(createLivenessCheck("liveness-2", true, builder -> builder.down())); healthCheckRegistry.register(createReadinessCheck("readiness-3", true, builder -> builder.up())); - CamelMicroProfileLivenessCheck livenessCheck = new CamelMicroProfileLivenessCheck(); - livenessCheck.setCamelContext(context); - reporter.addHealthCheck(livenessCheck); - - SmallRyeHealth health = reporter.getHealth(); + SmallRyeHealth health = reporter.getLiveness(); JsonObject healthObject = getHealthJson(health); assertEquals(Status.DOWN.name(), healthObject.getString("status")); JsonArray checks = healthObject.getJsonArray("checks"); - assertEquals(1, checks.size()); + assertEquals(2, checks.size()); - JsonObject checksObject = checks.getJsonObject(0); - assertHealthCheckOutput("camel-liveness-checks", Status.DOWN, checksObject, checksJson -> { - assertEquals(Status.UP.name(), checksJson.getString("liveness-1")); - assertEquals(Status.DOWN.name(), checksJson.getString("liveness-2")); - }); + assertHealthCheckOutput("liveness-1", Status.UP, checks.getJsonObject(0)); + assertHealthCheckOutput("liveness-2", Status.DOWN, checks.getJsonObject(1)); } @Test @@ -140,22 +126,16 @@ public void testCamelMicroProfileReadinessCheckUpStatus() { healthCheckRegistry.register(createReadinessCheck("readiness-1", true, builder -> builder.up())); healthCheckRegistry.register(createReadinessCheck("readiness-2", true, builder -> builder.up())); - CamelMicroProfileReadinessCheck readinessCheck = new CamelMicroProfileReadinessCheck(); - readinessCheck.setCamelContext(context); - reporter.addHealthCheck(readinessCheck); - - SmallRyeHealth health = reporter.getHealth(); + SmallRyeHealth health = reporter.getReadiness(); JsonObject healthObject = getHealthJson(health); assertEquals(Status.UP.name(), healthObject.getString("status")); JsonArray checks = healthObject.getJsonArray("checks"); - assertEquals(1, checks.size()); + assertEquals(2, checks.size()); - assertHealthCheckOutput("camel-readiness-checks", Status.UP, checks.getJsonObject(0), jsonObject -> { - assertEquals(Status.UP.name(), jsonObject.getString("readiness-1")); - assertEquals(Status.UP.name(), jsonObject.getString("readiness-2")); - }); + assertHealthCheckOutput("readiness-2", Status.UP, checks.getJsonObject(0)); + assertHealthCheckOutput("readiness-1", Status.UP, checks.getJsonObject(1)); } @Test @@ -166,22 +146,16 @@ public void testCamelMicroProfileReadinessCheckDownStatus() { healthCheckRegistry.register(createReadinessCheck("readiness-1", true, builder -> builder.up())); healthCheckRegistry.register(createReadinessCheck("readiness-2", true, builder -> builder.down())); - CamelMicroProfileReadinessCheck readinessCheck = new CamelMicroProfileReadinessCheck(); - readinessCheck.setCamelContext(context); - reporter.addHealthCheck(readinessCheck); - - SmallRyeHealth health = reporter.getHealth(); + SmallRyeHealth health = reporter.getReadiness(); JsonObject healthObject = getHealthJson(health); assertEquals(Status.DOWN.name(), healthObject.getString("status")); JsonArray checks = healthObject.getJsonArray("checks"); - assertEquals(1, checks.size()); + assertEquals(2, checks.size()); - assertHealthCheckOutput("camel-readiness-checks", Status.DOWN, checks.getJsonObject(0), jsonObject -> { - assertEquals(Status.UP.name(), jsonObject.getString("readiness-1")); - assertEquals(Status.DOWN.name(), jsonObject.getString("readiness-2")); - }); + assertHealthCheckOutput("readiness-2", Status.DOWN, checks.getJsonObject(0)); + assertHealthCheckOutput("readiness-1", Status.UP, checks.getJsonObject(1)); } @Test @@ -190,47 +164,163 @@ public void testCamelHealthCheckDisabled() { healthCheckRegistry.register(createReadinessCheck("disabled-check", false, builder -> builder.up())); - CamelMicroProfileReadinessCheck readinessCheck = new CamelMicroProfileReadinessCheck(); - readinessCheck.setCamelContext(context); - reporter.addHealthCheck(readinessCheck); - SmallRyeHealth health = reporter.getHealth(); JsonObject healthObject = getHealthJson(health); assertEquals(Status.UP.name(), healthObject.getString("status")); + JsonArray checks = healthObject.getJsonArray("checks"); + assertEquals(0, checks.size()); + } + + @Test + public void testCamelHealthCheckDisabledAtRuntime() { + HealthCheckRegistry healthCheckRegistry = HealthCheckRegistry.get(context); + + HealthCheck readinessCheck = createReadinessCheck("disabled-check", true, builder -> builder.up()); + healthCheckRegistry.register(readinessCheck); + readinessCheck.getConfiguration().setEnabled(false); + + SmallRyeHealth health = reporter.getHealth(); + JsonObject healthObject = getHealthJson(health); + assertEquals(Status.UP.name(), healthObject.getString("status")); JsonArray checks = healthObject.getJsonArray("checks"); assertEquals(1, checks.size()); - assertHealthCheckOutput("camel-readiness-checks", Status.UP, checks.getJsonObject(0), jsonObject -> { - assertNull(jsonObject); + assertHealthCheckOutput("disabled-check", Status.UP, checks.getJsonObject(0), jsonObject -> { + assertFalse(jsonObject.getBoolean("check.enabled")); }); } @Test public void testNoCamelHealthChecksRegistered() { - CamelMicroProfileReadinessCheck readinessCheck = new CamelMicroProfileReadinessCheck(); - readinessCheck.setCamelContext(context); - reporter.addHealthCheck(readinessCheck); + SmallRyeHealth health = reporter.getHealth(); + + JsonObject healthObject = getHealthJson(health); + assertEquals(Status.UP.name(), healthObject.getString("status")); - CamelMicroProfileLivenessCheck livenessCheck = new CamelMicroProfileLivenessCheck(); - livenessCheck.setCamelContext(context); - reporter.addHealthCheck(livenessCheck); + JsonArray checks = healthObject.getJsonArray("checks"); + assertEquals(0, checks.size()); + } + + @Test + public void testHealthCheckMultipleRegistrations() { + HealthCheckRegistry healthCheckRegistry = HealthCheckRegistry.get(context); + HealthCheck check = new AbstractHealthCheck("test-check") { + @Override + protected void doCall(HealthCheckResultBuilder builder, Map options) { + builder.up(); + } + }; + + for (int i = 0; i < 5; i++) { + healthCheckRegistry.register(check); + } + + SmallRyeHealth health = reporter.getHealth(); + + JsonObject healthObject = getHealthJson(health); + assertEquals(Status.UP.name(), healthObject.getString("status")); + JsonArray checks = healthObject.getJsonArray("checks"); + assertEquals(2, checks.size()); + + assertHealthCheckOutput("test-check", Status.UP, checks.getJsonObject(0)); + assertHealthCheckOutput("test-check", Status.UP, checks.getJsonObject(1)); + } + + @Test + public void testHealthCheckMultipleUnregisters() { + HealthCheckRegistry healthCheckRegistry = HealthCheckRegistry.get(context); + HealthCheck check = new AbstractHealthCheck("test-check") { + @Override + protected void doCall(HealthCheckResultBuilder builder, Map options) { + builder.up(); + } + }; + healthCheckRegistry.register(check); SmallRyeHealth health = reporter.getHealth(); JsonObject healthObject = getHealthJson(health); assertEquals(Status.UP.name(), healthObject.getString("status")); + JsonArray checks = healthObject.getJsonArray("checks"); + assertEquals(2, checks.size()); + + assertHealthCheckOutput("test-check", Status.UP, checks.getJsonObject(0)); + + assertHealthCheckOutput("test-check", Status.UP, checks.getJsonObject(1)); + for (int i = 0; i < 5; i++) { + healthCheckRegistry.unregister(check); + } + + health = reporter.getHealth(); + healthObject = getHealthJson(health); + assertEquals(Status.UP.name(), healthObject.getString("status")); + checks = healthObject.getJsonArray("checks"); + assertEquals(0, checks.size()); + } + + @Test + public void testHealthCheckUncheckedException() { + String errorMessage = "Forced exception"; + + HealthCheckRegistry healthCheckRegistry = HealthCheckRegistry.get(context); + HealthCheck check = new AbstractHealthCheck("exception-check") { + @Override + protected void doCall(HealthCheckResultBuilder builder, Map options) { + throw new IllegalStateException(errorMessage); + } + }; + healthCheckRegistry.register(check); + + SmallRyeHealth health = reporter.getHealth(); + + JsonObject healthObject = getHealthJson(health); + assertEquals(Status.DOWN.name(), healthObject.getString("status")); + JsonArray checks = healthObject.getJsonArray("checks"); + assertEquals(2, checks.size()); + + assertHealthCheckOutput(CamelMicroProfileHealthCheck.class.getName(), Status.DOWN, checks.getJsonObject(0), + jsonObject -> { + assertEquals(errorMessage, jsonObject.getString("rootCause")); + }); + + assertHealthCheckOutput(CamelMicroProfileHealthCheck.class.getName(), Status.DOWN, checks.getJsonObject(1), + jsonObject -> { + assertEquals(errorMessage, jsonObject.getString("rootCause")); + }); + } + + @Test + public void testHealthCheckCheckedException() { + String errorMessage = "Forced exception"; + + HealthCheckRegistry healthCheckRegistry = HealthCheckRegistry.get(context); + HealthCheck check = new AbstractHealthCheck("exception-check") { + @Override + protected void doCall(HealthCheckResultBuilder builder, Map options) { + builder.error(new Exception(errorMessage)); + builder.down(); + } + }; + healthCheckRegistry.register(check); + + SmallRyeHealth health = reporter.getHealth(); + + JsonObject healthObject = getHealthJson(health); + assertEquals(Status.DOWN.name(), healthObject.getString("status")); JsonArray checks = healthObject.getJsonArray("checks"); assertEquals(2, checks.size()); - assertHealthCheckOutput("camel-readiness-checks", Status.UP, checks.getJsonObject(0), jsonObject -> { - assertNull(jsonObject); + assertHealthCheckOutput("exception-check", Status.DOWN, checks.getJsonObject(0), jsonObject -> { + assertEquals(errorMessage, jsonObject.getString("error.message")); + assertNotNull(jsonObject.getString("error.stacktrace")); }); - assertHealthCheckOutput("camel-liveness-checks", Status.UP, checks.getJsonObject(1), jsonObject -> { - assertNull(jsonObject); + assertHealthCheckOutput("exception-check", Status.DOWN, checks.getJsonObject(1), jsonObject -> { + assertEquals(errorMessage, jsonObject.getString("error.message")); + assertNotNull(jsonObject.getString("error.stacktrace")); }); } } diff --git a/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthConsumerTest.java b/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthConsumerTest.java index 6928bccf229de..09e2d0def297e 100644 --- a/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthConsumerTest.java +++ b/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthConsumerTest.java @@ -21,12 +21,13 @@ import io.smallrye.health.SmallRyeHealth; import org.apache.camel.RoutesBuilder; +import org.apache.camel.ServiceStatus; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.health.HealthCheckRegistry; import org.eclipse.microprofile.health.HealthCheckResponse.Status; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import static org.apache.camel.microprofile.health.CamelMicroProfileHealthCheckRegistry.CONSUMERS_CHECK_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; public class CamelMicroProfileHealthConsumerTest extends CamelMicroProfileHealthTestSupport { @@ -38,10 +39,6 @@ public void testCamelHealthRepositoryUpStatus() { Object hc = healthCheckRegistry.resolveById("consumers"); healthCheckRegistry.register(hc); - CamelMicroProfileReadinessCheck readinessCheck = new CamelMicroProfileReadinessCheck(); - readinessCheck.setCamelContext(context); - reporter.addHealthCheck(readinessCheck); - SmallRyeHealth health = reporter.getHealth(); JsonObject healthObject = getHealthJson(health); @@ -50,14 +47,7 @@ public void testCamelHealthRepositoryUpStatus() { JsonArray checks = healthObject.getJsonArray("checks"); assertEquals(1, checks.size()); - Assertions.assertNotNull(checks.getJsonObject(0).getJsonObject("data")); - Assertions.assertEquals("healthyRoute", checks.getJsonObject(0).getJsonObject("data").getString("route.id")); - - assertHealthCheckOutput("camel-readiness-checks", Status.UP, checks.getJsonObject(0), jsonObject -> { - assertEquals(Status.UP.name(), jsonObject.getString("consumer:healthyRoute")); - assertEquals("healthyRoute", jsonObject.getString("route.id")); - assertEquals("Started", jsonObject.getString("route.status")); - }); + assertHealthCheckOutput(CONSUMERS_CHECK_NAME, Status.UP, checks.getJsonObject(0)); } @Test @@ -67,10 +57,6 @@ public void testCamelHealthRepositoryDownStatus() throws Exception { Object hc = healthCheckRegistry.resolveById("consumers"); healthCheckRegistry.register(hc); - CamelMicroProfileReadinessCheck readinessCheck = new CamelMicroProfileReadinessCheck(); - readinessCheck.setCamelContext(context); - reporter.addHealthCheck(readinessCheck); - context.getRouteController().stopRoute("healthyRoute"); SmallRyeHealth health = reporter.getHealth(); @@ -81,13 +67,39 @@ public void testCamelHealthRepositoryDownStatus() throws Exception { JsonArray checks = healthObject.getJsonArray("checks"); assertEquals(1, checks.size()); - assertHealthCheckOutput("camel-readiness-checks", Status.DOWN, checks.getJsonObject(0), jsonObject -> { - assertEquals(Status.DOWN.name(), jsonObject.getString("consumer:healthyRoute")); + assertHealthCheckOutput(CONSUMERS_CHECK_NAME, Status.DOWN, checks.getJsonObject(0), jsonObject -> { assertEquals("healthyRoute", jsonObject.getString("route.id")); - assertEquals("Stopped", jsonObject.getString("route.status")); + assertEquals(ServiceStatus.Stopped.name(), jsonObject.getString("route.status")); }); } + @Test + public void testRoutesRepositoryUnregister() { + HealthCheckRegistry healthCheckRegistry = HealthCheckRegistry.get(context); + Object hc = healthCheckRegistry.resolveById("consumers"); + healthCheckRegistry.register(hc); + + SmallRyeHealth health = reporter.getHealth(); + + JsonObject healthObject = getHealthJson(health); + assertEquals(Status.UP.name(), healthObject.getString("status")); + + JsonArray checks = healthObject.getJsonArray("checks"); + assertEquals(1, checks.size()); + + assertHealthCheckOutput(CONSUMERS_CHECK_NAME, Status.UP, checks.getJsonObject(0)); + + healthCheckRegistry.unregister(hc); + + health = reporter.getHealth(); + + healthObject = getHealthJson(health); + assertEquals(Status.UP.name(), healthObject.getString("status")); + + checks = healthObject.getJsonArray("checks"); + assertEquals(0, checks.size()); + } + @Override protected RoutesBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { diff --git a/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthRegistryBindingTest.java b/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthRegistryBindingTest.java new file mode 100644 index 0000000000000..c3ecca55962b0 --- /dev/null +++ b/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthRegistryBindingTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.microprofile.health; + +import javax.json.JsonArray; +import javax.json.JsonObject; + +import io.smallrye.health.SmallRyeHealth; +import org.apache.camel.BindToRegistry; +import org.apache.camel.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CamelMicroProfileHealthRegistryBindingTest extends CamelMicroProfileHealthTestSupport { + + @BindToRegistry + private HealthCheck check = createReadinessCheck("registry-binding-check", true, builder -> builder.up()); + + @Test + public void testHealthCheckCamelRegistryDiscovery() { + SmallRyeHealth health = reporter.getHealth(); + + JsonObject healthObject = getHealthJson(health); + assertEquals(HealthCheckResponse.Status.UP.name(), healthObject.getString("status")); + JsonArray checks = healthObject.getJsonArray("checks"); + assertEquals(1, checks.size()); + } +} diff --git a/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthTestSupport.java b/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthTestSupport.java index 6a0651f401e85..be436956c8bd3 100644 --- a/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthTestSupport.java +++ b/components/camel-microprofile/camel-microprofile-health/src/test/java/org/apache/camel/microprofile/health/CamelMicroProfileHealthTestSupport.java @@ -22,57 +22,61 @@ import java.util.Map; import java.util.function.Consumer; +import javax.inject.Inject; import javax.json.Json; import javax.json.JsonObject; import javax.json.stream.JsonParser; -import io.smallrye.health.AsyncHealthCheckFactory; +import io.smallrye.config.inject.ConfigExtension; import io.smallrye.health.SmallRyeHealth; import io.smallrye.health.SmallRyeHealthReporter; -import io.smallrye.health.registry.LivenessHealthRegistry; -import io.smallrye.health.registry.ReadinessHealthRegistry; -import io.smallrye.health.registry.StartupHealthRegistry; -import io.smallrye.health.registry.WellnessHealthRegistry; +import org.apache.camel.CamelContext; import org.apache.camel.health.HealthCheck; +import org.apache.camel.health.HealthCheckRegistry; import org.apache.camel.health.HealthCheckResultBuilder; +import org.apache.camel.impl.health.AbstractHealthCheck; import org.apache.camel.test.junit5.CamelTestSupport; -import org.apache.camel.util.ReflectionHelper; import org.eclipse.microprofile.health.HealthCheckResponse; -import org.junit.jupiter.api.BeforeEach; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.EnableAutoWeld; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +@EnableAutoWeld +@AddExtensions(ConfigExtension.class) public class CamelMicroProfileHealthTestSupport extends CamelTestSupport { - protected SmallRyeHealthReporter reporter; + @Inject + SmallRyeHealthReporter reporter; @Override - @BeforeEach - public void setUp() throws Exception { - super.setUp(); - reporter = new SmallRyeHealthReporter(); - - // we do not run in CDI so we have to setup this with reflection - ReflectionHelper.setField(reporter.getClass().getDeclaredField("asyncHealthCheckFactory"), reporter, - new AsyncHealthCheckFactory()); - ReflectionHelper.setField(reporter.getClass().getDeclaredField("livenessHealthRegistry"), reporter, - new LivenessHealthRegistry()); - ReflectionHelper.setField(reporter.getClass().getDeclaredField("readinessHealthRegistry"), reporter, - new ReadinessHealthRegistry()); - ReflectionHelper.setField(reporter.getClass().getDeclaredField("wellnessHealthRegistry"), reporter, - new WellnessHealthRegistry()); - ReflectionHelper.setField(reporter.getClass().getDeclaredField("startupHealthRegistry"), reporter, - new StartupHealthRegistry()); - ReflectionHelper.setField(reporter.getClass().getDeclaredField("timeoutSeconds"), reporter, 60); + protected CamelContext createCamelContext() throws Exception { + CamelContext camelContext = super.createCamelContext(); + HealthCheckRegistry registry = new CamelMicroProfileHealthCheckRegistry(); + camelContext.setExtension(HealthCheckRegistry.class, registry); + return camelContext; } protected void assertHealthCheckOutput( - String expectedName, HealthCheckResponse.Status expectedState, JsonObject healthObject, + String expectedName, + HealthCheckResponse.Status expectedState, + JsonObject healthObject) { + assertHealthCheckOutput(expectedName, expectedState, healthObject, null); + } + + protected void assertHealthCheckOutput( + String expectedName, + HealthCheckResponse.Status expectedState, + JsonObject healthObject, Consumer dataObjectAssertions) { + assertEquals(expectedName, healthObject.getString("name")); assertEquals(expectedState.name(), healthObject.getString("status")); - dataObjectAssertions.accept(healthObject.getJsonObject("data")); + + if (dataObjectAssertions != null) { + dataObjectAssertions.accept(healthObject.getJsonObject("data")); + } } protected JsonObject getHealthJson(SmallRyeHealth health) { @@ -89,24 +93,42 @@ protected String getHealthOutput(SmallRyeHealth health) { } protected HealthCheck createLivenessCheck(String id, boolean enabled, Consumer consumer) { - HealthCheck healthCheck = new AbstractCamelMicroProfileLivenessCheck(id) { + HealthCheck livenessCheck = new AbstractHealthCheck(id) { @Override protected void doCall(HealthCheckResultBuilder builder, Map options) { consumer.accept(builder); } + + @Override + public boolean isReadiness() { + return false; + } }; - healthCheck.getConfiguration().setEnabled(enabled); - return healthCheck; + livenessCheck.getConfiguration().setEnabled(enabled); + return livenessCheck; } protected HealthCheck createReadinessCheck(String id, boolean enabled, Consumer consumer) { - HealthCheck healthCheck = new AbstractCamelMicroProfileReadinessCheck(id) { + HealthCheck readinessCheck = new AbstractHealthCheck(id) { @Override protected void doCall(HealthCheckResultBuilder builder, Map options) { consumer.accept(builder); } + + @Override + public boolean isLiveness() { + return false; + } }; - healthCheck.getConfiguration().setEnabled(enabled); - return healthCheck; + readinessCheck.getConfiguration().setEnabled(enabled); + return readinessCheck; + + } + + /** + * Dump health check status to stdout, useful for debugging. + */ + protected void dumpHealth(SmallRyeHealth health) { + reporter.reportHealth(System.out, health); } } diff --git a/core/camel-health/src/main/java/org/apache/camel/impl/health/DefaultHealthCheckRegistry.java b/core/camel-health/src/main/java/org/apache/camel/impl/health/DefaultHealthCheckRegistry.java index 29b38c8f3745e..7ef3c37e38a8b 100644 --- a/core/camel-health/src/main/java/org/apache/camel/impl/health/DefaultHealthCheckRegistry.java +++ b/core/camel-health/src/main/java/org/apache/camel/impl/health/DefaultHealthCheckRegistry.java @@ -59,7 +59,6 @@ public DefaultHealthCheckRegistry() { public DefaultHealthCheckRegistry(CamelContext camelContext) { this.checks = new CopyOnWriteArraySet<>(); this.repositories = new CopyOnWriteArraySet<>(); - this.repositories.add(new HealthCheckRegistryRepository()); setCamelContext(camelContext); } @@ -88,6 +87,14 @@ public void setEnabled(boolean enabled) { protected void doInit() throws Exception { super.doInit(); + Optional hcr = repositories.stream() + .filter(repository -> repository instanceof HealthCheckRegistryRepository) + .findFirst(); + + if (!hcr.isPresent()) { + register(new HealthCheckRegistryRepository()); + } + for (HealthCheck check : checks) { CamelContextAware.trySetCamelContext(check, camelContext); } diff --git a/parent/pom.xml b/parent/pom.xml index bad84934cbbfd..9df544fded0ae 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -397,7 +397,6 @@ 1.8.1 2.0 3.0 - 3.1 3.0 0.3.7 26.0-jre @@ -541,6 +540,7 @@ 3.6.0 0.8.0 3.1.8.Final + 2.0.2.Final 1.11.4.Final 2.27.2 4.4.1