diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 38414472abf6..1c73bf485d72 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -42,6 +42,7 @@ updates: - dependency-name: "com.squareup.okhttp3:okhttp" - dependency-name: "io.swagger.codegen.v3:swagger-codegen-generators" - dependency-name: "org.amqphub.quarkus:quarkus-qpid-jms-bom" + - dependency-name: "org.jolokia:jolokia-agent-jvm" # Test dependencies - dependency-name: "org.wiremock:wiremock-standalone" - dependency-name: "com.unboundid:unboundid-ldapsdk" diff --git a/catalog/pom.xml b/catalog/pom.xml index efb8d130f49b..ebe892d5b9fa 100644 --- a/catalog/pom.xml +++ b/catalog/pom.xml @@ -2086,6 +2086,19 @@ + + org.apache.camel.quarkus + camel-quarkus-jolokia + ${project.version} + pom + test + + + * + * + + + org.apache.camel.quarkus camel-quarkus-jolt diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 15fce24d7a53..ee6196da74b0 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -183,6 +183,7 @@ *** xref:reference/extensions/javascript.adoc[JavaScript] *** xref:reference/extensions/jfr.adoc[Jfr] *** xref:reference/extensions/jira.adoc[Jira] +*** xref:reference/extensions/jolokia.adoc[Jolokia] *** xref:reference/extensions/json-patch.adoc[JsonPatch] *** xref:reference/extensions/kafka.adoc[Kafka] *** xref:reference/extensions/kamelet.adoc[Kamelet] diff --git a/docs/modules/ROOT/pages/reference/extensions/jolokia.adoc b/docs/modules/ROOT/pages/reference/extensions/jolokia.adoc new file mode 100644 index 000000000000..67d3cf7c3f66 --- /dev/null +++ b/docs/modules/ROOT/pages/reference/extensions/jolokia.adoc @@ -0,0 +1,266 @@ +// Do not edit directly! +// This file was generated by camel-quarkus-maven-plugin:update-extension-doc-page +[id="extensions-jolokia"] += Jolokia +:linkattrs: +:cq-artifact-id: camel-quarkus-jolokia +:cq-native-supported: false +:cq-status: Preview +:cq-status-deprecation: Preview +:cq-description: Expose runtime metrics and management operations via JMX with Jolokia +:cq-deprecated: false +:cq-jvm-since: 3.19.0 +:cq-native-since: n/a + +ifeval::[{doc-show-badges} == true] +[.badges] +[.badge-key]##JVM since##[.badge-supported]##3.19.0## [.badge-key]##Native##[.badge-unsupported]##unsupported## +endif::[] + +Expose runtime metrics and management operations via JMX with Jolokia + +[id="extensions-jolokia-maven-coordinates"] +== Maven coordinates + +[source,xml] +---- + + org.apache.camel.quarkus + camel-quarkus-jolokia + +---- +ifeval::[{doc-show-user-guide-link} == true] +Check the xref:user-guide/index.adoc[User guide] for more information about writing Camel Quarkus applications. +endif::[] + +[id="extensions-jolokia-usage"] +== Usage +This extension adds https://jolokia.org/[Jolokia] support to your application. + +[id="extensions-jolokia-usage-jolokia-http-endpoints"] +=== Jolokia HTTP endpoints + +In prod mode, Jolokia is accessible at the following URLs. + +* http://0.0.0.0:8778/jolokia/ +* http://0.0.0.0:8080/q/jolokia + +In dev and test modes Jolokia is bound only to `localhost`. + +To disable exposing Jolokia via the Quarkus management interface at `/q/jolokia`, add the following configuration to `application.properties`. + +[source] +---- +quarkus.camel.jolokia.register-management-endpoint=false +---- + +If you want to disable Jolokia entirely, then add the following configuration to `application.properties`. + +[source] +---- +quarkus.camel.jolokia.enabled=false +---- + +[id="extensions-jolokia-usage-jolokia-configuration"] +=== Jolokia configuration + +Any of the https://jolokia.org/reference/html/manual/agents.html[Jolokia configuration options] can be configured via the `quarkus.camel.jolokia.additional-properties.` option. +Where `` is the name of the Jolokia configuration option you want to set. + +For example, the following configuration added to `application.properties` enables Jolokia debugging and sets the max depth for traversing bean properties. + +[source] +---- +quarkus.camel.jolokia.additional-properties.debug=true +quarkus.camel.jolokia.additional-properties.maxDepth=10 +---- + +[id="extensions-jolokia-usage-jolokia-restrictor"] +=== Jolokia restrictor + +By default, a Jolokia restrictor is automatically registered that exposes access to only a specific set of MBean domains. + +* `org.apache.camel` +* `java.lang` +* `java.nio` + +If this is too restrictive, then you can either disable the default restrictor, or create your own custom restrictor. + +[id="extensions-jolokia-usage-disable-the-default-restrictor"] +==== Disable the default restrictor + +The following configuration added to `application.properties` disables the default restrictor. + +[source] +---- +quarkus.camel.jolokia.register-camel-restrictor=false +---- + +[id="extensions-jolokia-usage-create-a-custom-restrictor"] +==== Create a custom restrictor + +You can create your own restrictor class and register it with Jolokia. + +[source,java] +---- +public class CustomRestrictor extends AllowAllRestrictor { + // Override methods to apply custom restrictions +} +---- + +Register the restrictor with Jolokia by adding the following configuration to `application.properties`. + +[source] +---- +quarkus.camel.jolokia.additional-properties.restrictorClass=org.acme.CustomRestrictor +---- + +[id="extensions-jolokia-usage-kubernetes-openshift-support"] +=== Kubernetes & OpenShift support + +[id="extensions-jolokia-usage-generated-kubernetes-manifests"] +==== Generated Kubernetes manifests + +If the `quarkus-kubernetes` or `quarkus-openshift` extensions are present, a container port named `jolokia` will be added automatically to the pod configuration within the generated Kubernetes manifest resources. + +This can be disabled by adding the following configuration to `application.properties`. + +[source] +---- +quarkus.camel.jolokia.kubernetes.expose-container-port=false +---- + +[id="extensions-jolokia-usage-automatic-enablement-of-ssl-client-authentication"] +==== Automatic enablement of SSL client authentication + +If the application detects that it is running on Kubernetes or OpenShift, then Jolokia is automatically configured for SSL client authentication. +This is useful if you use tools like https://hawt.io/[Hawtio] to discover and connect to your running application pod. + +This functionality can be disabled by adding the following configuration to `application.properties`. + +[source] +---- +quarkus.camel.jolokia.kubernetes.client-authentication-enabled=false +---- + +Note that if you choose to use https://github.com/hawtio/hawtio-online[hawtio-online] to connect to your running application, then you must configure the Jolokia client principal. + +[source] +---- +quarkus.camel.jolokia.kubernetes.client-principal="cn=hawtio-online.hawtio.svc" +---- + + +[id="extensions-jolokia-additional-camel-quarkus-configuration"] +== Additional Camel Quarkus configuration + +[width="100%",cols="80,5,15",options="header"] +|=== +| Configuration property | Type | Default + + +|icon:lock[title=Fixed at build time] [[quarkus.camel.jolokia.enabled]]`link:#quarkus.camel.jolokia.enabled[quarkus.camel.jolokia.enabled]` + +Enables Jolokia support. +| `boolean` +| `true` + +|icon:lock[title=Fixed at build time] [[quarkus.camel.jolokia.path]]`link:#quarkus.camel.jolokia.path[quarkus.camel.jolokia.path]` + +The context path that the Jolokia agent is deployed under. +| `string` +| `jolokia` + +|icon:lock[title=Fixed at build time] [[quarkus.camel.jolokia.register-management-endpoint]]`link:#quarkus.camel.jolokia.register-management-endpoint[quarkus.camel.jolokia.register-management-endpoint]` + +Whether to register a Quarkus management endpoint for Jolokia (default /q/jolokia). +When enabled this activates a management endpoint which will be accessible on a path relative to +${quarkus.http.non-application-root-path}/${quarkus.camel.jolokia.server.path}. +If the management interface is enabled, the value will be resolved as a path relative to +${quarkus.management.root-path}/${quarkus.camel.jolokia.server.path}. Note that for this feature to work you must +have quarkus-vertx-http on the application classpath. +| `boolean` +| `true` + +|icon:lock[title=Fixed at build time] [[quarkus.camel.jolokia.kubernetes.expose-container-port]]`link:#quarkus.camel.jolokia.kubernetes.expose-container-port[quarkus.camel.jolokia.kubernetes.expose-container-port]` + +When {@code true} and the quarkus-kubernetes extension is present, a container port named jolokia will +be added to the generated Kubernetes manifests within the container spec ports definition. +| `boolean` +| `true` + +| [[quarkus.camel.jolokia.server.auto-start]]`link:#quarkus.camel.jolokia.server.auto-start[quarkus.camel.jolokia.server.auto-start]` + +Whether the Jolokia agent HTTP server should be started automatically. +When set to {@code false}, it is the user responsibility to start the server. +This can be done via {@code @Inject CamelQuarkusJolokiaServer} and then invoking the start() method. +| `boolean` +| `true` + +| [[quarkus.camel.jolokia.server.host]]`link:#quarkus.camel.jolokia.server.host[quarkus.camel.jolokia.server.host]` + +The host address to which the Jolokia agent HTTP server should bind to. +When unspecified, the default is localhost for dev and test mode. +In prod mode the default is to bind to all interfaces at 0.0.0.0. +| `string` +| + +| [[quarkus.camel.jolokia.server.port]]`link:#quarkus.camel.jolokia.server.port[quarkus.camel.jolokia.server.port]` + +The port on which the Jolokia agent HTTP server should listen on. +| `int` +| `8778` + +| [[quarkus.camel.jolokia.server.discovery-enabled-mode]]`link:#quarkus.camel.jolokia.server.discovery-enabled-mode[quarkus.camel.jolokia.server.discovery-enabled-mode]` + +The mode in which Jolokia agent discovery is enabled. The default {@code dev-test}, enables discovery only in dev and +test modes. +A value of {@code all} enables agent discovery in dev, test and prod modes. Setting the value to {@code none} will +disable agent discovery in all modes. +| `all`, `dev-test`, `none` +| `dev-test` + +| [[quarkus.camel.jolokia.kubernetes.client-authentication-enabled]]`link:#quarkus.camel.jolokia.kubernetes.client-authentication-enabled[quarkus.camel.jolokia.kubernetes.client-authentication-enabled]` + +Whether to enable Jolokia SSL client authentication in Kubernetes environments. +Useful for tools such as hawtio to be able to connect with your application. +| `boolean` +| `true` + +| [[quarkus.camel.jolokia.kubernetes.service-ca-cert]]`link:#quarkus.camel.jolokia.kubernetes.service-ca-cert[quarkus.camel.jolokia.kubernetes.service-ca-cert]` + +Absolute path of the CA certificate Jolokia should use for SSL client authentication. +| link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/File.html[`File`] +| `/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt` + +| [[quarkus.camel.jolokia.kubernetes.client-principal]]`link:#quarkus.camel.jolokia.kubernetes.client-principal[quarkus.camel.jolokia.kubernetes.client-principal]` + +The principal which must be given in a client certificate to allow access to Jolokia. +| `string` +| + +| [[quarkus.camel.jolokia.additional-properties.-additional-properties]]`link:#quarkus.camel.jolokia.additional-properties.-additional-properties[quarkus.camel.jolokia.additional-properties."additional-properties"]` + +Arbitrary Jolokia configuration options. These are described at the +Jolokia documentation. +Options can be configured like {@code quarkus.camel.jolokia.additional-properties."debug"=true}. +| `Map` +| + +| [[quarkus.camel.jolokia.register-camel-restrictor]]`link:#quarkus.camel.jolokia.register-camel-restrictor[quarkus.camel.jolokia.register-camel-restrictor]` + +When {@code true}, a Jolokia restrictor is registered that limits MBean read, write and operation execution to the +following MBean domains. +
    +
  • org.apache.camel
  • +
  • java.lang
  • +
  • java.nio
  • +
+Note that this option has no effect if quarkus.camel.jolokia.additional-properties."restrictorClass" is set. +| `boolean` +| `true` +|=== + +[.configuration-legend] +{doc-link-icon-lock}[title=Fixed at build time] Configuration property fixed at build time. All other configuration properties are overridable at runtime. + diff --git a/extensions-jvm/jolokia/deployment/pom.xml b/extensions-jvm/jolokia/deployment/pom.xml new file mode 100644 index 000000000000..78960c937a35 --- /dev/null +++ b/extensions-jvm/jolokia/deployment/pom.xml @@ -0,0 +1,90 @@ + + + + 4.0.0 + + org.apache.camel.quarkus + camel-quarkus-jolokia-parent + 3.19.0-SNAPSHOT + ../pom.xml + + + camel-quarkus-jolokia-deployment + Camel Quarkus :: Jolokia :: Deployment + + + + io.quarkus + quarkus-vertx-http-deployment + + + io.quarkus + quarkus-kubernetes-spi + + + org.apache.camel.quarkus + camel-quarkus-management-deployment + + + org.apache.camel.quarkus + camel-quarkus-jolokia + + + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test + + + org.apache.camel + camel-direct + test + + + org.apache.camel + camel-seda + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/extensions-jvm/jolokia/deployment/src/main/java/org/apache/camel/quarkus/jolokia/deployment/JolokiaProcessor.java b/extensions-jvm/jolokia/deployment/src/main/java/org/apache/camel/quarkus/jolokia/deployment/JolokiaProcessor.java new file mode 100644 index 000000000000..8e3b026d8d60 --- /dev/null +++ b/extensions-jvm/jolokia/deployment/src/main/java/org/apache/camel/quarkus/jolokia/deployment/JolokiaProcessor.java @@ -0,0 +1,156 @@ +/* + * 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.quarkus.jolokia.deployment; + +import java.util.function.BooleanSupplier; + +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; +import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.IsNormal; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild; +import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; +import io.quarkus.vertx.http.deployment.BodyHandlerBuildItem; +import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; +import io.quarkus.vertx.http.deployment.RouteBuildItem; +import jakarta.enterprise.context.ApplicationScoped; +import org.apache.camel.quarkus.core.JvmOnlyRecorder; +import org.apache.camel.quarkus.jolokia.CamelQuarkusJolokiaServer; +import org.apache.camel.quarkus.jolokia.JolokiaRecorder; +import org.apache.camel.quarkus.jolokia.config.JolokiaBuildTimeConfig; +import org.apache.camel.quarkus.jolokia.config.JolokiaRuntimeConfig; +import org.jboss.logging.Logger; + +@BuildSteps(onlyIf = JolokiaProcessor.JolokiaEnabled.class) +public class JolokiaProcessor { + private static final Logger LOG = Logger.getLogger(JolokiaProcessor.class); + private static final String FEATURE = "camel-jolokia"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + JolokiaServerConfigBuildItem createJolokiaServerConfig( + JolokiaBuildTimeConfig buildTimeConfig, + JolokiaRuntimeConfig config, + JolokiaRecorder recorder) { + return new JolokiaServerConfigBuildItem(recorder.createJolokiaServerConfig(config, buildTimeConfig.path())); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + JolokiaServerBuildItem createJolokiaServer( + JolokiaServerConfigBuildItem jolokiaServerConfig, + JolokiaRecorder recorder) { + return new JolokiaServerBuildItem(recorder.createJolokiaServer(jolokiaServerConfig.getRuntimeValue())); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + void startJolokiaServer( + JolokiaServerBuildItem jolokiaServer, + JolokiaRuntimeConfig runtimeConfig, + BuildProducer syntheticBean, + JolokiaRecorder recorder) { + recorder.startJolokiaServer(jolokiaServer.getRuntimeValue(), runtimeConfig); + syntheticBean.produce(SyntheticBeanBuildItem.configure(CamelQuarkusJolokiaServer.class) + .scope(ApplicationScoped.class) + .runtimeValue(recorder.createJolokiaServerBean(jolokiaServer.getRuntimeValue())) + .setRuntimeInit() + .done()); + } + + @BuildStep(onlyIfNot = { IsNormal.class, IsDevelopment.class }) + @Record(ExecutionTime.RUNTIME_INIT) + void registerJolokiaServerShutdownHook( + JolokiaServerBuildItem jolokiaServer, + ShutdownContextBuildItem shutdownContextBuildItem, + JolokiaRecorder recorder) { + recorder.registerJolokiaServerShutdownHook(jolokiaServer.getRuntimeValue(), shutdownContextBuildItem); + } + + @BuildStep(onlyIf = JolokiaManagementEndpointEnabled.class) + @Record(ExecutionTime.RUNTIME_INIT) + void createManagementRoute( + JolokiaServerConfigBuildItem jolokiaServerConfig, + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, + BodyHandlerBuildItem bodyHandler, + Capabilities capabilities, + BuildProducer routes, + JolokiaBuildTimeConfig buildTimeConfig, + JolokiaRecorder recorder) { + + if (capabilities.isPresent(Capability.VERTX_HTTP)) { + String jolokiaEndpointPath = nonApplicationRootPathBuildItem.resolvePath(buildTimeConfig.path()); + routes.produce(nonApplicationRootPathBuildItem.routeBuilder() + .management() + .routeFunction(buildTimeConfig.path() + "/*", recorder.route(bodyHandler.getHandler())) + .handler(recorder.getHandler(jolokiaServerConfig.getRuntimeValue(), jolokiaEndpointPath)) + .build()); + } + } + + @BuildStep(onlyIf = { IsNormal.class, ExposeContainerPortEnabled.class }) + KubernetesPortBuildItem configureJolokiaKubernetesPort() { + return KubernetesPortBuildItem.fromRuntimeConfiguration("jolokia", "quarkus.camel.jolokia.server.port", 8778, true); + } + + @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class) + @Record(value = ExecutionTime.RUNTIME_INIT) + void warnJvmInNative(JvmOnlyRecorder recorder) { + JvmOnlyRecorder.warnJvmInNative(LOG, FEATURE); + recorder.warnJvmInNative(FEATURE); + } + + static final class JolokiaEnabled implements BooleanSupplier { + JolokiaBuildTimeConfig config; + + @Override + public boolean getAsBoolean() { + return config.enabled(); + } + } + + static final class JolokiaManagementEndpointEnabled implements BooleanSupplier { + JolokiaBuildTimeConfig config; + + @Override + public boolean getAsBoolean() { + return config.registerManagementEndpoint(); + } + } + + static final class ExposeContainerPortEnabled implements BooleanSupplier { + JolokiaBuildTimeConfig config; + + @Override + public boolean getAsBoolean() { + return config.kubernetes().exposeContainerPort(); + } + } +} diff --git a/extensions-jvm/jolokia/deployment/src/main/java/org/apache/camel/quarkus/jolokia/deployment/JolokiaServerBuildItem.java b/extensions-jvm/jolokia/deployment/src/main/java/org/apache/camel/quarkus/jolokia/deployment/JolokiaServerBuildItem.java new file mode 100644 index 000000000000..7cba6e98db10 --- /dev/null +++ b/extensions-jvm/jolokia/deployment/src/main/java/org/apache/camel/quarkus/jolokia/deployment/JolokiaServerBuildItem.java @@ -0,0 +1,33 @@ +/* + * 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.quarkus.jolokia.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.runtime.RuntimeValue; +import org.jolokia.jvmagent.JolokiaServer; + +final class JolokiaServerBuildItem extends SimpleBuildItem { + private final RuntimeValue server; + + JolokiaServerBuildItem(RuntimeValue server) { + this.server = server; + } + + RuntimeValue getRuntimeValue() { + return server; + } +} diff --git a/extensions-jvm/jolokia/deployment/src/main/java/org/apache/camel/quarkus/jolokia/deployment/JolokiaServerConfigBuildItem.java b/extensions-jvm/jolokia/deployment/src/main/java/org/apache/camel/quarkus/jolokia/deployment/JolokiaServerConfigBuildItem.java new file mode 100644 index 000000000000..2c9ef758caf3 --- /dev/null +++ b/extensions-jvm/jolokia/deployment/src/main/java/org/apache/camel/quarkus/jolokia/deployment/JolokiaServerConfigBuildItem.java @@ -0,0 +1,33 @@ +/* + * 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.quarkus.jolokia.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.runtime.RuntimeValue; +import org.jolokia.jvmagent.JolokiaServerConfig; + +final class JolokiaServerConfigBuildItem extends SimpleBuildItem { + private final RuntimeValue config; + + JolokiaServerConfigBuildItem(RuntimeValue config) { + this.config = config; + } + + public RuntimeValue getRuntimeValue() { + return config; + } +} diff --git a/extensions-jvm/jolokia/deployment/src/test/java/org/apache/camel/quarkus/jolokia/JolokiaCustomContextPathTest.java b/extensions-jvm/jolokia/deployment/src/test/java/org/apache/camel/quarkus/jolokia/JolokiaCustomContextPathTest.java new file mode 100644 index 000000000000..6965c44c9521 --- /dev/null +++ b/extensions-jvm/jolokia/deployment/src/test/java/org/apache/camel/quarkus/jolokia/JolokiaCustomContextPathTest.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.quarkus.jolokia; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class JolokiaCustomContextPathTest { + @RegisterExtension + static final QuarkusUnitTest CONFIG = new QuarkusUnitTest() + .withEmptyApplication() + .overrideConfigKey("quarkus.camel.jolokia.path", "test"); + + @Test + void contextPathAccessible() { + RestAssured.port = 8778; + RestAssured.get("/test/") + .then() + .statusCode(200); + } + + @Test + void managementEndpointContextPathAccessible() { + RestAssured.get("/q/test") + .then() + .statusCode(200); + } +} diff --git a/extensions-jvm/jolokia/deployment/src/test/java/org/apache/camel/quarkus/jolokia/JolokiaDisabledTest.java b/extensions-jvm/jolokia/deployment/src/test/java/org/apache/camel/quarkus/jolokia/JolokiaDisabledTest.java new file mode 100644 index 000000000000..97c71b5ab112 --- /dev/null +++ b/extensions-jvm/jolokia/deployment/src/test/java/org/apache/camel/quarkus/jolokia/JolokiaDisabledTest.java @@ -0,0 +1,38 @@ +/* + * 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.quarkus.jolokia; + +import java.net.ConnectException; +import java.net.URI; + +import io.quarkus.test.QuarkusUnitTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class JolokiaDisabledTest { + @RegisterExtension + static final QuarkusUnitTest CONFIG = new QuarkusUnitTest() + .withEmptyApplication() + .overrideConfigKey("quarkus.camel.jolokia.enabled", "false"); + + @Test + void jolokiaUnreachable() { + URI uri = URI.create("http://localhost:8778/jolokia"); + Assertions.assertThrows(ConnectException.class, () -> uri.toURL().openConnection().connect()); + } +} diff --git a/extensions-jvm/jolokia/deployment/src/test/java/org/apache/camel/quarkus/jolokia/JolokiaEnabledTest.java b/extensions-jvm/jolokia/deployment/src/test/java/org/apache/camel/quarkus/jolokia/JolokiaEnabledTest.java new file mode 100644 index 000000000000..cdfe1122de29 --- /dev/null +++ b/extensions-jvm/jolokia/deployment/src/test/java/org/apache/camel/quarkus/jolokia/JolokiaEnabledTest.java @@ -0,0 +1,90 @@ +/* + * 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.quarkus.jolokia; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import io.restassured.response.Response; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.apache.camel.ConsumerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +class JolokiaEnabledTest { + @RegisterExtension + static final QuarkusUnitTest CONFIG = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(Routes.class)); + + @Inject + ConsumerTemplate consumerTemplate; + + @Test + void getCamelContextMBean() { + RestAssured.port = 8778; + RestAssured.get("/jolokia/read/org.apache.camel:context=camel-1,type=context,name=\"camel-1\"") + .then() + .statusCode(200) + .body( + "value.UptimeMillis", greaterThan(0), + "value.TotalRoutes", equalTo(1)); + } + + @Test + void sendMessageToRoute() { + String jolokiaPayload = "{\"type\":\"exec\",\"mbean\":\"org.apache.camel:context=camel-1,type=context,name=\\\"camel-1\\\"\",\"operation\":\"sendStringBody(java.lang.String, java.lang.String)\",\"arguments\":[\"direct://start\",\"Hello World\"]}"; + Response response = RestAssured.given() + .contentType(ContentType.JSON) + .body(jolokiaPayload) + // Test the Quarkus management endpoint returns a redirect to the Jolokia server + .post("/q/jolokia/"); + + if (response.statusCode() == 301) { + String newUrl = response.getHeader("Location"); + + RestAssured.given() + .body(jolokiaPayload) + .post(newUrl) + .then() + .statusCode(200) + .body("status", equalTo(200)); + } else { + fail("Unexpected status code: " + response.statusCode()); + } + + String message = consumerTemplate.receiveBody("seda:end", 10000, String.class); + assertEquals("Hello World", message); + } + + @ApplicationScoped + public static class Routes extends RouteBuilder { + @Override + public void configure() throws Exception { + from("direct:start").to("seda:end"); + } + } +} diff --git a/extensions-jvm/jolokia/deployment/src/test/java/org/apache/camel/quarkus/jolokia/JolokiaManagementEndpointDisabledTest.java b/extensions-jvm/jolokia/deployment/src/test/java/org/apache/camel/quarkus/jolokia/JolokiaManagementEndpointDisabledTest.java new file mode 100644 index 000000000000..983354f211c8 --- /dev/null +++ b/extensions-jvm/jolokia/deployment/src/test/java/org/apache/camel/quarkus/jolokia/JolokiaManagementEndpointDisabledTest.java @@ -0,0 +1,36 @@ +/* + * 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.quarkus.jolokia; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class JolokiaManagementEndpointDisabledTest { + @RegisterExtension + static final QuarkusUnitTest CONFIG = new QuarkusUnitTest() + .withEmptyApplication() + .overrideConfigKey("quarkus.camel.jolokia.register-management-endpoint", "false"); + + @Test + void managementEndpointUnreachable() { + RestAssured.get("/q/jolokia") + .then() + .statusCode(404); + } +} diff --git a/extensions-jvm/jolokia/pom.xml b/extensions-jvm/jolokia/pom.xml new file mode 100644 index 000000000000..c553672392b9 --- /dev/null +++ b/extensions-jvm/jolokia/pom.xml @@ -0,0 +1,37 @@ + + + + 4.0.0 + + org.apache.camel.quarkus + camel-quarkus-extensions-jvm + 3.19.0-SNAPSHOT + ../pom.xml + + + camel-quarkus-jolokia-parent + Camel Quarkus :: Jolokia + pom + + + deployment + runtime + + diff --git a/extensions-jvm/jolokia/runtime/pom.xml b/extensions-jvm/jolokia/runtime/pom.xml new file mode 100644 index 000000000000..3c8d75a1058b --- /dev/null +++ b/extensions-jvm/jolokia/runtime/pom.xml @@ -0,0 +1,88 @@ + + + + 4.0.0 + + org.apache.camel.quarkus + camel-quarkus-jolokia-parent + 3.19.0-SNAPSHOT + ../pom.xml + + + camel-quarkus-jolokia + Camel Quarkus :: Jolokia :: Runtime + Expose runtime metrics and management operations via JMX with Jolokia + + + 3.19.0 + + + + + io.quarkus + quarkus-vertx-http + + + org.apache.camel.quarkus + camel-quarkus-management + + + org.jolokia + jolokia-agent-jvm + + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + org.jolokia:jolokia-agent-jvm + org.jolokia:jolokia-json + org.jolokia:jolokia-server-core + org.jolokia:jolokia-server-detector + org.jolokia:jolokia-service-discovery + org.jolokia:jolokia-service-history + org.jolokia:jolokia-service-notif-pull + org.jolokia:jolokia-service-notif-sse + org.jolokia:jolokia-service-jmx + org.jolokia:jolokia-service-jsr160 + org.jolokia:jolokia-service-serializer + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/extensions-jvm/jolokia/runtime/src/main/doc/usage.adoc b/extensions-jvm/jolokia/runtime/src/main/doc/usage.adoc new file mode 100644 index 000000000000..4da0826ee92c --- /dev/null +++ b/extensions-jvm/jolokia/runtime/src/main/doc/usage.adoc @@ -0,0 +1,106 @@ +This extension adds https://jolokia.org/[Jolokia] support to your application. + +=== Jolokia HTTP endpoints + +In prod mode, Jolokia is accessible at the following URLs. + +* http://0.0.0.0:8778/jolokia/ +* http://0.0.0.0:8080/q/jolokia + +In dev and test modes Jolokia is bound only to `localhost`. + +To disable exposing Jolokia via the Quarkus management interface at `/q/jolokia`, add the following configuration to `application.properties`. + +[source] +---- +quarkus.camel.jolokia.register-management-endpoint=false +---- + +If you want to disable Jolokia entirely, then add the following configuration to `application.properties`. + +[source] +---- +quarkus.camel.jolokia.enabled=false +---- + +=== Jolokia configuration + +Any of the https://jolokia.org/reference/html/manual/agents.html[Jolokia configuration options] can be configured via the `quarkus.camel.jolokia.additional-properties.` option. +Where `` is the name of the Jolokia configuration option you want to set. + +For example, the following configuration added to `application.properties` enables Jolokia debugging and sets the max depth for traversing bean properties. + +[source] +---- +quarkus.camel.jolokia.additional-properties.debug=true +quarkus.camel.jolokia.additional-properties.maxDepth=10 +---- + +=== Jolokia restrictor + +By default, a Jolokia restrictor is automatically registered that exposes access to only a specific set of MBean domains. + +* `org.apache.camel` +* `java.lang` +* `java.nio` + +If this is too restrictive, then you can either disable the default restrictor, or create your own custom restrictor. + +==== Disable the default restrictor + +The following configuration added to `application.properties` disables the default restrictor. + +[source] +---- +quarkus.camel.jolokia.register-camel-restrictor=false +---- + +==== Create a custom restrictor + +You can create your own restrictor class and register it with Jolokia. + +[source,java] +---- +public class CustomRestrictor extends AllowAllRestrictor { + // Override methods to apply custom restrictions +} +---- + +Register the restrictor with Jolokia by adding the following configuration to `application.properties`. + +[source] +---- +quarkus.camel.jolokia.additional-properties.restrictorClass=org.acme.CustomRestrictor +---- + +=== Kubernetes & OpenShift support + +==== Generated Kubernetes manifests + +If the `quarkus-kubernetes` or `quarkus-openshift` extensions are present, a container port named `jolokia` will be added automatically to the pod configuration within the generated Kubernetes manifest resources. + +This can be disabled by adding the following configuration to `application.properties`. + +[source] +---- +quarkus.camel.jolokia.kubernetes.expose-container-port=false +---- + +==== Automatic enablement of SSL client authentication + +If the application detects that it is running on Kubernetes or OpenShift, then Jolokia is automatically configured for SSL client authentication. +This is useful if you use tools like https://hawt.io/[Hawtio] to discover and connect to your running application pod. + +This functionality can be disabled by adding the following configuration to `application.properties`. + +[source] +---- +quarkus.camel.jolokia.kubernetes.client-authentication-enabled=false +---- + +Note that if you choose to use https://github.com/hawtio/hawtio-online[hawtio-online] to connect to your running application, then you must configure the Jolokia client principal. + +[source] +---- +quarkus.camel.jolokia.kubernetes.client-principal="cn=hawtio-online.hawtio.svc" +---- diff --git a/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/CamelQuarkusJolokiaLogHandler.java b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/CamelQuarkusJolokiaLogHandler.java new file mode 100644 index 000000000000..3b66e757eaad --- /dev/null +++ b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/CamelQuarkusJolokiaLogHandler.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.quarkus.jolokia; + +import org.jboss.logging.Logger; +import org.jolokia.server.core.service.api.LogHandler; + +final class CamelQuarkusJolokiaLogHandler implements LogHandler { + private static final Logger LOG = Logger.getLogger(CamelQuarkusJolokiaLogHandler.class); + + @Override + public void debug(String s) { + LOG.debug(s); + } + + @Override + public void info(String s) { + LOG.info(s); + } + + @Override + public void error(String s, Throwable throwable) { + LOG.error(s); + } + + @Override + public boolean isDebug() { + return LOG.isDebugEnabled(); + } +} diff --git a/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/CamelQuarkusJolokiaServer.java b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/CamelQuarkusJolokiaServer.java new file mode 100644 index 000000000000..90ac1c6c2a0f --- /dev/null +++ b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/CamelQuarkusJolokiaServer.java @@ -0,0 +1,35 @@ +/* + * 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.quarkus.jolokia; + +import org.jolokia.jvmagent.JolokiaServer; + +public class CamelQuarkusJolokiaServer { + private final JolokiaServer server; + + CamelQuarkusJolokiaServer(JolokiaServer server) { + this.server = server; + } + + public synchronized void start() { + this.server.start(); + } + + public synchronized void stop() { + this.server.stop(); + } +} diff --git a/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/JolokiaRecorder.java b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/JolokiaRecorder.java new file mode 100644 index 000000000000..da1d9be1460f --- /dev/null +++ b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/JolokiaRecorder.java @@ -0,0 +1,179 @@ +/* + * 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.quarkus.jolokia; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +import io.quarkus.runtime.LaunchMode; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.ShutdownContext; +import io.quarkus.runtime.annotations.Recorder; +import io.vertx.core.Handler; +import io.vertx.ext.web.Route; +import io.vertx.ext.web.RoutingContext; +import org.apache.camel.quarkus.jolokia.config.JolokiaRuntimeConfig; +import org.apache.camel.quarkus.jolokia.config.JolokiaRuntimeConfig.DiscoveryEnabledMode; +import org.apache.camel.quarkus.jolokia.config.JolokiaRuntimeConfig.Kubernetes; +import org.apache.camel.quarkus.jolokia.config.JolokiaRuntimeConfig.Server; +import org.apache.camel.quarkus.jolokia.restrictor.CamelJolokiaRestrictor; +import org.apache.camel.util.CollectionHelper; +import org.apache.camel.util.HostUtils; +import org.apache.camel.util.ObjectHelper; +import org.eclipse.microprofile.config.ConfigProvider; +import org.jboss.logging.Logger; +import org.jolokia.jvmagent.JolokiaServer; +import org.jolokia.jvmagent.JolokiaServerConfig; +import org.jolokia.server.core.config.ConfigKey; + +import static io.smallrye.common.os.Linux.isWSL; + +@Recorder +public class JolokiaRecorder { + private static final String ALL_INTERFACES = "0.0.0.0"; + private static final String LOCALHOST = "localhost"; + private static final Logger LOG = Logger.getLogger(JolokiaRequestRedirectHandler.class); + + public Consumer route(Handler bodyHandler) { + return new Consumer() { + @Override + public void accept(Route route) { + route.handler(bodyHandler).produces("application/json"); + } + }; + } + + public RuntimeValue createJolokiaServerConfig( + JolokiaRuntimeConfig runtimeConfig, + String endpointPath) { + + Server server = runtimeConfig.server(); + Kubernetes kubernetes = runtimeConfig.kubernetes(); + + // Configure Jolokia HTTP server host, port & context path + String host = runtimeConfig.server().host().orElse(null); + if (ObjectHelper.isEmpty(host)) { + if (LaunchMode.isRemoteDev()) { + host = ALL_INTERFACES; + } else if (LaunchMode.current().isDevOrTest()) { + if (!isWSL()) { + host = LOCALHOST; + } else { + host = ALL_INTERFACES; + } + } else { + host = ALL_INTERFACES; + } + } + + Map serverOptions = new HashMap<>(); + serverOptions.put("host", host); + serverOptions.put("port", String.valueOf(server.port())); + serverOptions.put(ConfigKey.AGENT_CONTEXT.getKeyValue(), "/" + endpointPath); + + // Attempt Kubernetes configuration + Optional kubernetesServiceHost = ConfigProvider.getConfig().getOptionalValue("kubernetes.service.host", + String.class); + if (kubernetesServiceHost.isPresent()) { + if (kubernetes.clientAuthenticationEnabled() && kubernetes.serviceCaCert().exists()) { + serverOptions.put(ConfigKey.DISCOVERY_ENABLED.getKeyValue(), "false"); + serverOptions.put("protocol", "https"); + serverOptions.put("useSslClientAuthentication", "true"); + serverOptions.put("extendedClientCheck", "true"); + serverOptions.put("caCert", kubernetes.serviceCaCert().getAbsolutePath()); + kubernetes.clientPrincipal() + .ifPresent(clientPrincipal -> serverOptions.put("clientPrincipal", clientPrincipal)); + } else { + LOG.warnf("Kubernetes service CA certificate %s does not exist", kubernetes.serviceCaCert()); + } + } + + // Merge configuration with any arbitrary values provided via quarkus.camel.jolokia.additional-properties + Map combinedOptions = CollectionHelper.mergeMaps(serverOptions, + runtimeConfig.additionalProperties()); + + // Configure CamelJolokiaRestrictor if an existing restrictor is not already provided + if (runtimeConfig.registerCamelRestrictor()) { + combinedOptions.putIfAbsent(ConfigKey.RESTRICTOR_CLASS.getKeyValue(), CamelJolokiaRestrictor.class.getName()); + } + + // Enable discovery based on the provided mode + DiscoveryEnabledMode discoveryMode = server.discoveryEnabledMode(); + if (discoveryMode != DiscoveryEnabledMode.NONE) { + if ((discoveryMode == DiscoveryEnabledMode.ALL) + || (discoveryMode == DiscoveryEnabledMode.DEV_TEST && LaunchMode.current().isDevOrTest())) { + combinedOptions.putIfAbsent(ConfigKey.DISCOVERY_ENABLED.getKeyValue(), "true"); + } + } + + return new RuntimeValue<>(new JolokiaServerConfig(combinedOptions)); + } + + public RuntimeValue createJolokiaServer(RuntimeValue serverConfig) { + try { + CamelQuarkusJolokiaAgent agent = new CamelQuarkusJolokiaAgent(serverConfig.getValue()); + return new RuntimeValue<>(agent); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void startJolokiaServer(RuntimeValue jolokiaServer, JolokiaRuntimeConfig config) { + if (config.server().autoStart()) { + jolokiaServer.getValue().start(); + } + } + + public void registerJolokiaServerShutdownHook(RuntimeValue jolokiaServer, ShutdownContext shutdownContext) { + shutdownContext.addShutdownTask(() -> jolokiaServer.getValue().stop()); + } + + public RuntimeValue createJolokiaServerBean(RuntimeValue jolokiaServer) { + return new RuntimeValue<>(new CamelQuarkusJolokiaServer(jolokiaServer.getValue())); + } + + static final class CamelQuarkusJolokiaAgent extends JolokiaServer { + CamelQuarkusJolokiaAgent(JolokiaServerConfig config) throws IOException { + super(config, new CamelQuarkusJolokiaLogHandler()); + } + } + + public Handler getHandler(RuntimeValue config, String jolokiaEndpointPath) { + JolokiaServerConfig serverConfig = config.getValue(); + String host = resolveHost(serverConfig.getAddress()); + URI uri = URI.create("%s://%s:%d%s".formatted(serverConfig.getProtocol(), host, serverConfig.getPort(), + serverConfig.getContextPath())); + return new JolokiaRequestRedirectHandler(uri.normalize(), jolokiaEndpointPath); + } + + static String resolveHost(InetAddress address) { + if (address == null) { + try { + return HostUtils.getLocalHostName(); + } catch (UnknownHostException e) { + throw new IllegalStateException("Unable to determine the Jolokia host", e); + } + } + return address.getHostName(); + } +} diff --git a/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/JolokiaRequestRedirectHandler.java b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/JolokiaRequestRedirectHandler.java new file mode 100644 index 000000000000..69b1cc496b6f --- /dev/null +++ b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/JolokiaRequestRedirectHandler.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.quarkus.jolokia; + +import java.net.URI; + +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +/** + * Vert.x route to redirect from /q/jolokia to the Jolokia embedded HTTP server endpoint + */ +final class JolokiaRequestRedirectHandler implements Handler { + private final String jolokiaAgentBaseUrl; + private final String jolokiaManagementEndpointPath; + + public JolokiaRequestRedirectHandler(URI jolokiaAgentBaseUrl, String jolokiaManagementEndpointPath) { + this.jolokiaManagementEndpointPath = jolokiaManagementEndpointPath; + this.jolokiaAgentBaseUrl = jolokiaAgentBaseUrl.toString(); + } + + @Override + public void handle(RoutingContext routingContext) { + String uri = routingContext.request().uri(); + routingContext.response() + .setStatusCode(301) + .putHeader("Location", jolokiaAgentBaseUrl + uri.replace(jolokiaManagementEndpointPath, "")) + .end(); + } +} diff --git a/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/config/JolokiaBuildTimeConfig.java b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/config/JolokiaBuildTimeConfig.java new file mode 100644 index 000000000000..afa7dcf18027 --- /dev/null +++ b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/config/JolokiaBuildTimeConfig.java @@ -0,0 +1,63 @@ +/* + * 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.quarkus.jolokia.config; + +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; + +@ConfigMapping(prefix = "quarkus.camel.jolokia") +@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public interface JolokiaBuildTimeConfig { + /** + * Enables Jolokia support. + */ + @WithDefault("true") + boolean enabled(); + + /** + * The context path that the Jolokia agent is deployed under. + */ + @WithDefault("jolokia") + String path(); + + /** + * Whether to register a Quarkus management endpoint for Jolokia (default /q/jolokia). + * When enabled this activates a management endpoint which will be accessible on a path relative to + * ${quarkus.http.non-application-root-path}/${quarkus.camel.jolokia.server.path}. + * If the management interface is enabled, the value will be resolved as a path relative to + * ${quarkus.management.root-path}/${quarkus.camel.jolokia.server.path}. Note that for this feature to work you must + * have quarkus-vertx-http on the application classpath. + */ + @WithDefault("true") + boolean registerManagementEndpoint(); + + /** + * Jolokia Kubernetes build time configuration. + */ + Kubernetes kubernetes(); + + interface Kubernetes { + /** + * When {@code true} and the quarkus-kubernetes extension is present, a container port named jolokia will + * be added to the generated Kubernetes manifests within the container spec ports definition. + */ + @WithDefault("true") + boolean exposeContainerPort(); + } +} diff --git a/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/config/JolokiaRuntimeConfig.java b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/config/JolokiaRuntimeConfig.java new file mode 100644 index 000000000000..d250d9e89efb --- /dev/null +++ b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/config/JolokiaRuntimeConfig.java @@ -0,0 +1,118 @@ +/* + * 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.quarkus.jolokia.config; + +import java.io.File; +import java.util.Map; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; + +@ConfigMapping(prefix = "quarkus.camel.jolokia") +@ConfigRoot(phase = ConfigPhase.RUN_TIME) +public interface JolokiaRuntimeConfig { + /** + * Jolokia agent HTTP server configuration. + */ + Server server(); + + /** + * Kubernetes runtime configuration. + */ + Kubernetes kubernetes(); + + /** + * Arbitrary Jolokia configuration options. These are described at the + * Jolokia documentation. + * Options can be configured like {@code quarkus.camel.jolokia.additional-properties."debug"=true}. + */ + Map additionalProperties(); + + /** + * When {@code true}, a Jolokia restrictor is registered that limits MBean read, write and operation execution to the + * following MBean domains. + *
    + *
  • org.apache.camel
  • + *
  • java.lang
  • + *
  • java.nio
  • + *
+ * Note that this option has no effect if quarkus.camel.jolokia.additional-properties."restrictorClass" is set. + */ + @WithDefault("true") + boolean registerCamelRestrictor(); + + interface Server { + /** + * Whether the Jolokia agent HTTP server should be started automatically. + * When set to {@code false}, it is the user responsibility to start the server. + * This can be done via {@code @Inject CamelQuarkusJolokiaServer} and then invoking the start() method. + */ + @WithDefault("true") + boolean autoStart(); + + /** + * The host address to which the Jolokia agent HTTP server should bind to. + * When unspecified, the default is localhost for dev and test mode. + * In prod mode the default is to bind to all interfaces at 0.0.0.0. + */ + Optional host(); + + /** + * The port on which the Jolokia agent HTTP server should listen on. + */ + @WithDefault("8778") + int port(); + + /** + * The mode in which Jolokia agent discovery is enabled. The default {@code dev-test}, enables discovery only in dev and + * test modes. + * A value of {@code all} enables agent discovery in dev, test and prod modes. Setting the value to {@code none} will + * disable agent discovery in all modes. + */ + @WithDefault("DEV_TEST") + DiscoveryEnabledMode discoveryEnabledMode(); + } + + enum DiscoveryEnabledMode { + ALL, + DEV_TEST, + NONE, + } + + interface Kubernetes { + /** + * Whether to enable Jolokia SSL client authentication in Kubernetes environments. + * Useful for tools such as hawtio to be able to connect with your application. + */ + @WithDefault("true") + boolean clientAuthenticationEnabled(); + + /** + * Absolute path of the CA certificate Jolokia should use for SSL client authentication. + */ + @WithDefault("/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt") + File serviceCaCert(); + + /** + * The principal which must be given in a client certificate to allow access to Jolokia. + */ + Optional clientPrincipal(); + } +} diff --git a/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/restrictor/CamelJolokiaRestrictor.java b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/restrictor/CamelJolokiaRestrictor.java new file mode 100644 index 000000000000..43b5016792cb --- /dev/null +++ b/extensions-jvm/jolokia/runtime/src/main/java/org/apache/camel/quarkus/jolokia/restrictor/CamelJolokiaRestrictor.java @@ -0,0 +1,51 @@ +/* + * 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.quarkus.jolokia.restrictor; + +import java.util.List; + +import javax.management.ObjectName; + +import org.jolokia.server.core.restrictor.AllowAllRestrictor; + +public final class CamelJolokiaRestrictor extends AllowAllRestrictor { + private static final List ALLOWED_DOMAINS = List.of("org.apache.camel", "java.lang", "java.nio"); + + @Override + public boolean isAttributeReadAllowed(ObjectName objectName, String attribute) { + return isAllowedDomain(objectName); + } + + @Override + public boolean isAttributeWriteAllowed(ObjectName objectName, String attribute) { + return isAllowedDomain(objectName); + } + + @Override + public boolean isOperationAllowed(ObjectName objectName, String operation) { + return isAllowedDomain(objectName); + } + + @Override + public boolean isObjectNameHidden(ObjectName objectName) { + return !isAllowedDomain(objectName); + } + + private boolean isAllowedDomain(ObjectName objectName) { + return ALLOWED_DOMAINS.contains(objectName.getDomain()); + } +} diff --git a/extensions-jvm/jolokia/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions-jvm/jolokia/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 000000000000..93c5bd3ca645 --- /dev/null +++ b/extensions-jvm/jolokia/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,34 @@ +# +# 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. +# + +# This is a generated file. Do not edit directly! +# To re-generate, run the following command from the top level directory: +# +# mvn -N cq:update-quarkus-metadata +# +--- +name: "Camel Jolokia" +description: "Expose runtime metrics and management operations via JMX with Jolokia" +metadata: + icon-url: "https://raw.githubusercontent.com/apache/camel-website/main/antora-ui-camel/src/img/logo-d.svg" + sponsor: "Apache Software Foundation" + unlisted: true + guide: "https://camel.apache.org/camel-quarkus/latest/reference/extensions/jolokia.html" + categories: + - "integration" + status: + - "preview" diff --git a/extensions-jvm/pom.xml b/extensions-jvm/pom.xml index 6e44f46d8c4f..4d1631bc80a2 100644 --- a/extensions-jvm/pom.xml +++ b/extensions-jvm/pom.xml @@ -69,6 +69,7 @@ jcr jgroups jgroups-raft + jolokia jooq json-patch jsonapi diff --git a/integration-tests-jvm/jolokia/pom.xml b/integration-tests-jvm/jolokia/pom.xml new file mode 100644 index 000000000000..ce7645dd696e --- /dev/null +++ b/integration-tests-jvm/jolokia/pom.xml @@ -0,0 +1,132 @@ + + + + 4.0.0 + + org.apache.camel.quarkus + camel-quarkus-build-parent-it + 3.19.0-SNAPSHOT + ../../poms/build-parent-it/pom.xml + + + camel-quarkus-integration-test-jolokia + Camel Quarkus :: Integration Tests :: Jolokia + Integration tests for Camel Quarkus Jolokia extension + + + + org.apache.camel.quarkus + camel-quarkus-jolokia + + + org.apache.camel.quarkus + camel-quarkus-direct + + + org.apache.camel.quarkus + camel-quarkus-seda + + + io.quarkus + quarkus-resteasy + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + org.apache.camel.quarkus + camel-quarkus-integration-test-support + test + + + io.smallrye.certs + smallrye-certificate-generator-junit5 + test + + + + + + virtualDependencies + + + !noVirtualDependencies + + + + + + org.apache.camel.quarkus + camel-quarkus-direct-deployment + ${project.version} + pom + test + + + * + * + + + + + org.apache.camel.quarkus + camel-quarkus-jolokia-deployment + ${project.version} + pom + test + + + * + * + + + + + org.apache.camel.quarkus + camel-quarkus-seda-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + diff --git a/integration-tests-jvm/jolokia/src/main/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaResource.java b/integration-tests-jvm/jolokia/src/main/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaResource.java new file mode 100644 index 000000000000..f97be0810c0d --- /dev/null +++ b/integration-tests-jvm/jolokia/src/main/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaResource.java @@ -0,0 +1,56 @@ +/* + * 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.quarkus.component.jolokia.it; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.apache.camel.ConsumerTemplate; +import org.apache.camel.quarkus.jolokia.CamelQuarkusJolokiaServer; + +@Path("/jolokia") +@ApplicationScoped +public class JolokiaResource { + @Inject + ConsumerTemplate consumerTemplate; + + @Inject + CamelQuarkusJolokiaServer server; + + @Path("/message/get") + @Produces(MediaType.TEXT_PLAIN) + @GET + public String getMessage() { + return consumerTemplate.receiveBody("seda:end", 10000, String.class); + } + + @Path("/start") + @POST + public void start() { + server.start(); + } + + @Path("/stop") + @POST + public void stop() { + server.stop(); + } +} diff --git a/integration-tests-jvm/jolokia/src/main/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaRoutes.java b/integration-tests-jvm/jolokia/src/main/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaRoutes.java new file mode 100644 index 000000000000..439d1155d43b --- /dev/null +++ b/integration-tests-jvm/jolokia/src/main/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaRoutes.java @@ -0,0 +1,27 @@ +/* + * 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.quarkus.component.jolokia.it; + +import org.apache.camel.builder.RouteBuilder; + +public class JolokiaRoutes extends RouteBuilder { + @Override + public void configure() throws Exception { + from("direct:start") + .to("seda:end"); + } +} diff --git a/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaAdditionalPropertiesTest.java b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaAdditionalPropertiesTest.java new file mode 100644 index 000000000000..03d45d578ca8 --- /dev/null +++ b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaAdditionalPropertiesTest.java @@ -0,0 +1,53 @@ +/* + * 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.quarkus.component.jolokia.it; + +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.equalTo; + +@TestProfile(JolokiaAdditionalPropertiesTest.JolokiaAdditionalPropertiesProfile.class) +@QuarkusTest +class JolokiaAdditionalPropertiesTest { + @BeforeEach + public void beforeEach() { + RestAssured.port = 8778; + } + + @Test + void additionalProperties() { + RestAssured.given() + .get("/jolokia/") + .then() + .statusCode(200) + .body("value.config.maxDepth", equalTo("10")); + } + + public static final class JolokiaAdditionalPropertiesProfile implements QuarkusTestProfile { + @Override + public Map getConfigOverrides() { + return Map.of("quarkus.camel.jolokia.additional-properties.maxDepth", "10"); + } + } +} diff --git a/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaCustomHostPortTest.java b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaCustomHostPortTest.java new file mode 100644 index 000000000000..2c2421314278 --- /dev/null +++ b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaCustomHostPortTest.java @@ -0,0 +1,57 @@ +/* + * 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.quarkus.component.jolokia.it; + +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.equalTo; + +@TestProfile(JolokiaCustomHostPortTest.JolokiaAdditionalPropertiesProfile.class) +@QuarkusTest +class JolokiaCustomHostPortTest { + private static final int PORT = 8779; + + @BeforeEach + public void beforeEach() { + RestAssured.port = PORT; + } + + @Test + void customHostPort() throws InterruptedException { + RestAssured.given() + .get("/jolokia/") + .then() + .statusCode(200) + .body("status", equalTo(200)); + } + + public static final class JolokiaAdditionalPropertiesProfile implements QuarkusTestProfile { + @Override + public Map getConfigOverrides() { + return Map.of( + "quarkus.camel.jolokia.server.host", "0.0.0.0", + "quarkus.camel.jolokia.server.port", String.valueOf(PORT)); + } + } +} diff --git a/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaDisableAutoStartTest.java b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaDisableAutoStartTest.java new file mode 100644 index 000000000000..03cc29d16ca8 --- /dev/null +++ b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaDisableAutoStartTest.java @@ -0,0 +1,91 @@ +/* + * 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.quarkus.component.jolokia.it; + +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.time.Duration; +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import io.restassured.config.HttpClientConfig; +import org.awaitility.Awaitility; +import org.eclipse.microprofile.config.ConfigProvider; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@TestProfile(JolokiaDisableAutoStartTest.JolokiaAdditionalPropertiesProfile.class) +@QuarkusTest +class JolokiaDisableAutoStartTest { + @Test + void autoStartupDisabled() { + // Connecting to Jolokia should not be possible + RestAssured.config = RestAssured.config().httpClient( + HttpClientConfig.httpClientConfig() + .setParam("http.connection.timeout", 1000) + .setParam("http.socket.timeout", 1000)); + + RestAssured.port = 8778; + assertThrows(SocketTimeoutException.class, () -> { + RestAssured.get("/jolokia/") + .then() + .statusCode(204); + }); + + // Manually start Jolokia + RestAssured.port = ConfigProvider.getConfig().getValue("quarkus.http.test-port", Integer.class); + RestAssured.post("/jolokia/start") + .then() + .statusCode(204); + + // Verify a basic request is successful + RestAssured.port = 8778; + Awaitility.await().atMost(Duration.ofSeconds(5)).pollInterval(Duration.ofMillis(100)).untilAsserted(() -> { + RestAssured.given() + .get("/jolokia/") + .then() + .statusCode(200) + .body("status", equalTo(200)); + }); + + // Verify stop. We don't bother putting this in a finally block since a shutdown hook will take care of stopping Jolokia in case of test failure + RestAssured.port = ConfigProvider.getConfig().getValue("quarkus.http.test-port", Integer.class); + RestAssured.post("/jolokia/stop") + .then() + .statusCode(204); + + // Connecting to Jolokia should not be possible + RestAssured.port = 8778; + assertThrows(ConnectException.class, () -> { + RestAssured.get("/jolokia/") + .then() + .statusCode(204); + }); + } + + public static final class JolokiaAdditionalPropertiesProfile implements QuarkusTestProfile { + @Override + public Map getConfigOverrides() { + return Map.of("quarkus.camel.jolokia.server.auto-start", "false"); + } + } +} diff --git a/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaDisableRestrictorTest.java b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaDisableRestrictorTest.java new file mode 100644 index 000000000000..94e08bb9daca --- /dev/null +++ b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaDisableRestrictorTest.java @@ -0,0 +1,53 @@ +/* + * 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.quarkus.component.jolokia.it; + +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.emptyOrNullString; + +@TestProfile(JolokiaDisableRestrictorTest.JolokiaAdditionalPropertiesProfile.class) +@QuarkusTest +class JolokiaDisableRestrictorTest { + @BeforeEach + public void beforeEach() { + RestAssured.port = 8778; + } + + @Test + void restrictorClassUnconfigured() { + RestAssured.given() + .get("/jolokia/") + .then() + .statusCode(200) + .body("value.config.restrictorClass", emptyOrNullString()); + } + + public static final class JolokiaAdditionalPropertiesProfile implements QuarkusTestProfile { + @Override + public Map getConfigOverrides() { + return Map.of("quarkus.camel.jolokia.register-camel-restrictor", "false"); + } + } +} diff --git a/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaDiscoveryDisabledTest.java b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaDiscoveryDisabledTest.java new file mode 100644 index 000000000000..088c4dbc52a1 --- /dev/null +++ b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaDiscoveryDisabledTest.java @@ -0,0 +1,57 @@ +/* + * 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.quarkus.component.jolokia.it; + +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.equalTo; + +@TestProfile(JolokiaDiscoveryDisabledTest.JolokiaAdditionalPropertiesProfile.class) +@QuarkusTest +class JolokiaDiscoveryDisabledTest { + private static final int PORT = 8778; + + @BeforeEach + public void beforeEach() { + RestAssured.port = PORT; + } + + @Test + void discoveryEnabledFalse() throws InterruptedException { + RestAssured.given() + .get("/jolokia/") + .then() + .statusCode(200) + .body( + "status", equalTo(200), + "value.config.discoveryEnabled", equalTo("false")); + } + + public static final class JolokiaAdditionalPropertiesProfile implements QuarkusTestProfile { + @Override + public Map getConfigOverrides() { + return Map.of("quarkus.camel.jolokia.server.discovery-enabled-mode", "none"); + } + } +} diff --git a/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaKubernetesClientSSLTest.java b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaKubernetesClientSSLTest.java new file mode 100644 index 000000000000..9363b9fed54e --- /dev/null +++ b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaKubernetesClientSSLTest.java @@ -0,0 +1,93 @@ +/* + * 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.quarkus.component.jolokia.it; + +import java.io.File; +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import io.restassured.config.SSLConfig; +import io.smallrye.certs.Format; +import io.smallrye.certs.junit5.Certificate; +import io.smallrye.certs.junit5.Certificates; +import org.apache.http.NoHttpResponseException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Certificates(baseDir = "target/certs", certificates = { + @Certificate(name = "kubernetes-service-cert", formats = { Format.PKCS12, + Format.PEM }, password = "2s3cr3t") +}) +@TestProfile(JolokiaKubernetesClientSSLTest.JolokiaAdditionalPropertiesProfile.class) +@QuarkusTest +class JolokiaKubernetesClientSSLTest { + private static final File SERVER_CERT = new File("target/certs/kubernetes-service-cert.crt"); + private static final File SERVER_KEY = new File("target/certs/kubernetes-service-cert.key"); + private static final File CA_CERT = new File("target/certs/kubernetes-service-cert-ca.crt"); + + @BeforeEach + public void beforeEach() { + RestAssured.port = 8778; + } + + @Test + void clientSSLAuthentication() { + // Plain HTTP should be disabled + assertThrows(NoHttpResponseException.class, () -> { + RestAssured.given() + .get("/jolokia/") + .then() + .statusCode(200); + }); + + RestAssured.config = RestAssured.config().with().sslConfig(getSSLConfig()); + RestAssured.given() + .get("https://localhost:8778/jolokia/") + .then() + .statusCode(200) + .body( + "status", equalTo(200), + "value.details.secured", equalTo(true)); + } + + private SSLConfig getSSLConfig() { + return new SSLConfig() + .keystoreType("PKCS12") + .keyStore("target/certs/kubernetes-service-cert-keystore.p12", "2s3cr3t") + .trustStoreType("PKCS12") + .trustStore("target/certs/kubernetes-service-cert-truststore.p12", "2s3cr3t"); + } + + public static final class JolokiaAdditionalPropertiesProfile implements QuarkusTestProfile { + @Override + public Map getConfigOverrides() { + return Map.of( + "kubernetes.service.host", "fake-host", + "quarkus.camel.jolokia.kubernetes.service-ca-cert", CA_CERT.getAbsolutePath(), + "quarkus.camel.jolokia.kubernetes.client-principal", "cn=localhost", + "quarkus.camel.jolokia.additional-properties.serverCert", SERVER_CERT.getAbsolutePath(), + "quarkus.camel.jolokia.additional-properties.serverKey", SERVER_KEY.getAbsolutePath(), + "quarkus.camel.jolokia.additional-properties.caCert", CA_CERT.getAbsolutePath()); + } + } +} diff --git a/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaTest.java b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaTest.java new file mode 100644 index 000000000000..6437f36c7320 --- /dev/null +++ b/integration-tests-jvm/jolokia/src/test/java/org/apache/camel/quarkus/component/jolokia/it/JolokiaTest.java @@ -0,0 +1,67 @@ +/* + * 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.quarkus.component.jolokia.it; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.apache.camel.quarkus.jolokia.restrictor.CamelJolokiaRestrictor; +import org.eclipse.microprofile.config.ConfigProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.equalTo; + +@QuarkusTest +class JolokiaTest { + @BeforeEach + public void beforeEach() { + RestAssured.port = 8778; + } + + @Test + void defaultConfiguration() { + RestAssured.given() + .get("/jolokia/") + .then() + .statusCode(200) + .body( + "status", equalTo(200), + "value.config.discoveryEnabled", equalTo("true"), + "value.config.restrictorClass", equalTo(CamelJolokiaRestrictor.class.getName()), + "value.details.url", equalTo("http://127.0.0.1:8778/jolokia/")); + } + + @Test + void sendMessage() { + String jolokiaPayload = "{\"type\":\"exec\",\"mbean\":\"org.apache.camel:context=camel-1,type=context,name=\\\"camel-1\\\"\",\"operation\":\"sendStringBody(java.lang.String, java.lang.String)\",\"arguments\":[\"direct://start\",\"Hello World\"]}"; + RestAssured.given() + .contentType(ContentType.JSON) + .body(jolokiaPayload) + .post("/jolokia/") + .then() + .statusCode(200) + .body("status", equalTo(200)); + + RestAssured.port = ConfigProvider.getConfig().getValue("quarkus.http.test-port", Integer.class); + + RestAssured.get("/jolokia/message/get") + .then() + .statusCode(200) + .body(equalTo("Hello World")); + } +} diff --git a/integration-tests-jvm/pom.xml b/integration-tests-jvm/pom.xml index c322351a652c..bb8ba028cdca 100644 --- a/integration-tests-jvm/pom.xml +++ b/integration-tests-jvm/pom.xml @@ -68,6 +68,7 @@ jcr jgroups jgroups-raft + jolokia jooq json-patch jsonapi diff --git a/pom.xml b/pom.xml index bee26b1ffff3..28490c7fc5e4 100644 --- a/pom.xml +++ b/pom.xml @@ -119,6 +119,7 @@ 5.6.0 0.9.11 2.2.13 + 2.2.2 0.2.21 ${json-path-version} ${jedis-client-version} diff --git a/poms/bom/pom.xml b/poms/bom/pom.xml index 63e4afde0257..b35597702e7d 100644 --- a/poms/bom/pom.xml +++ b/poms/bom/pom.xml @@ -4506,6 +4506,16 @@ camel-quarkus-jms-deployment ${camel-quarkus.version}
+ + org.apache.camel.quarkus + camel-quarkus-jolokia + ${camel-quarkus.version} + + + org.apache.camel.quarkus + camel-quarkus-jolokia-deployment + ${camel-quarkus.version} + org.apache.camel.quarkus camel-quarkus-jolt @@ -7257,6 +7267,11 @@ kotlinx-serialization-core-jvm ${kotlinx.version} + + org.jolokia + jolokia-agent-jvm + ${jolokia.version} + org.json json diff --git a/poms/bom/src/main/generated/flattened-full-pom.xml b/poms/bom/src/main/generated/flattened-full-pom.xml index fb4ff3503fe7..665cb6aa7d8d 100644 --- a/poms/bom/src/main/generated/flattened-full-pom.xml +++ b/poms/bom/src/main/generated/flattened-full-pom.xml @@ -4431,6 +4431,16 @@ camel-quarkus-jms-deployment 3.19.0-SNAPSHOT + + org.apache.camel.quarkus + camel-quarkus-jolokia + 3.19.0-SNAPSHOT + + + org.apache.camel.quarkus + camel-quarkus-jolokia-deployment + 3.19.0-SNAPSHOT + org.apache.camel.quarkus camel-quarkus-jolt @@ -7179,6 +7189,11 @@ kotlinx-serialization-core-jvm 1.4.0 + + org.jolokia + jolokia-agent-jvm + 2.2.2 + org.json json diff --git a/poms/bom/src/main/generated/flattened-reduced-pom.xml b/poms/bom/src/main/generated/flattened-reduced-pom.xml index 0b95d4e96c8e..3e6f4253c119 100644 --- a/poms/bom/src/main/generated/flattened-reduced-pom.xml +++ b/poms/bom/src/main/generated/flattened-reduced-pom.xml @@ -4421,6 +4421,16 @@ camel-quarkus-jms-deployment 3.19.0-SNAPSHOT + + org.apache.camel.quarkus + camel-quarkus-jolokia + 3.19.0-SNAPSHOT + + + org.apache.camel.quarkus + camel-quarkus-jolokia-deployment + 3.19.0-SNAPSHOT + org.apache.camel.quarkus camel-quarkus-jolt @@ -7119,6 +7129,11 @@ kotlinx-serialization-core-jvm 1.4.0 + + org.jolokia + jolokia-agent-jvm + 2.2.2 + org.json json diff --git a/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml b/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml index c48e6d38bfec..03a39e847fd4 100644 --- a/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml +++ b/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml @@ -4421,6 +4421,16 @@ camel-quarkus-jms-deployment 3.19.0-SNAPSHOT + + org.apache.camel.quarkus + camel-quarkus-jolokia + 3.19.0-SNAPSHOT + + + org.apache.camel.quarkus + camel-quarkus-jolokia-deployment + 3.19.0-SNAPSHOT + org.apache.camel.quarkus camel-quarkus-jolt @@ -7119,6 +7129,11 @@ kotlinx-serialization-core-jvm 1.4.0 + + org.jolokia + jolokia-agent-jvm + 2.2.2 + org.json json