From 8e0edffd864b89ccf9f950d006e8d711032a0c4d Mon Sep 17 00:00:00 2001 From: Thomas Diesler Date: Tue, 30 Jul 2024 14:54:16 +0200 Subject: [PATCH] [CAMEL-21032] Generate k8s service for health endpoint --- .../templates/main-kubernetes-pom.tmpl | 15 +++++ .../templates/spring-boot-kubernetes-pom.tmpl | 15 +++++ .../jbang/core/commands/k/CamelKBaseTest.java | 2 +- .../commands/k/IntegrationExportTest.java | 27 +++++--- .../commands/kubernetes/KubernetesExport.java | 2 +- .../kubernetes/traits/ContainerTrait.java | 5 +- .../kubernetes/traits/ServiceTrait.java | 8 ++- .../kubernetes/traits/TraitHelper.java | 17 ++--- .../kubernetes/KubernetesExportTest.java | 67 ++++++++++++++----- .../src/test/resources/route-service.yaml | 2 +- 10 files changed, 121 insertions(+), 39 deletions(-) diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main-kubernetes-pom.tmpl b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main-kubernetes-pom.tmpl index 4d0e44bc1ed4f..261ca1ca492b1 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main-kubernetes-pom.tmpl +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main-kubernetes-pom.tmpl @@ -188,6 +188,21 @@ + + camel.debug + + + camel.debug + true + + + + + org.apache.camel + camel-debug + + + diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/spring-boot-kubernetes-pom.tmpl b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/spring-boot-kubernetes-pom.tmpl index 916f0de418ebc..56f0816566708 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/spring-boot-kubernetes-pom.tmpl +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/spring-boot-kubernetes-pom.tmpl @@ -139,6 +139,21 @@ + + camel.debug + + + camel.debug + true + + + + + org.apache.camel + camel-debug + + + diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/CamelKBaseTest.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/CamelKBaseTest.java index 06f4df9cd5b94..b0cf56a53eb1f 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/CamelKBaseTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/CamelKBaseTest.java @@ -65,7 +65,7 @@ public void setupFixtures() { } @BeforeEach - public void setup() { + public void setup() throws Exception { printer = new StringPrinter(); k8sServer.reset(); } diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExportTest.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExportTest.java index bc5f9685e3ea9..37ad037b4875e 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExportTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExportTest.java @@ -21,22 +21,25 @@ import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; +import java.util.List; +import java.util.Optional; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.apps.Deployment; +import org.apache.camel.RuntimeCamelException; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; -import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait; import org.apache.camel.dsl.jbang.core.common.RuntimeType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class IntegrationExportTest { +class IntegrationExportTest extends CamelKBaseTest { private File workingDir; @BeforeEach - public void setup() throws IOException { + public void setup() throws Exception { workingDir = Files.createTempDirectory("camel-k-integration-export").toFile(); workingDir.deleteOnExit(); } @@ -50,7 +53,7 @@ public void shouldExportFromIntegration() throws Exception { Assertions.assertEquals(0, exit); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(RuntimeType.quarkus); Assertions.assertEquals("routes", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); Assertions.assertEquals("routes", deployment.getMetadata().getLabels().get(BaseTrait.INTEGRATION_LABEL)); @@ -75,7 +78,7 @@ public void shouldExportFromPipe() throws Exception { Assertions.assertEquals(0, exit); - Deployment deployment = getDeployment(workingDir); + Deployment deployment = getDeployment(RuntimeType.quarkus); Assertions.assertEquals("timer-to-log", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); Assertions.assertEquals("timer-to-log", deployment.getMetadata().getLabels().get(BaseTrait.INTEGRATION_LABEL)); @@ -98,10 +101,18 @@ private IntegrationExport createCommand(String[] files, String exportDir) { RuntimeType.quarkus, files, exportDir, "camel-test", false); } - private Deployment getDeployment(File workingDir) throws IOException { + private Deployment getDeployment(RuntimeType rt) throws IOException { + return getResource(rt, Deployment.class) + .orElseThrow(() -> new RuntimeCamelException("Cannot find deployment for: %s".formatted(rt.runtime()))); + } + + private Optional getResource(RuntimeType rt, Class type) throws IOException { try (FileInputStream fis = new FileInputStream(new File(workingDir, "src/main/kubernetes/kubernetes.yml"))) { - return KubernetesHelper.yaml().loadAs(fis, Deployment.class); + List resources = kubernetesClient.load(fis).items(); + return resources.stream() + .filter(it -> type.isAssignableFrom(it.getClass())) + .map(type::cast) + .findFirst(); } } - } diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java index f91ea10be3606..039b767591a6d 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java @@ -238,7 +238,7 @@ public Integer export() throws Exception { buildProperties.add("%s.kubernetes.image-name=%s".formatted(propPrefix, container.getImage())); buildProperties.add("%s.kubernetes.ports.%s.container-port=%d".formatted(propPrefix, Optional.ofNullable(container.getPortName()).orElse(ContainerTrait.DEFAULT_CONTAINER_PORT_NAME), - Optional.ofNullable(container.getPort()).orElse(ContainerTrait.DEFAULT_CONTAINER_PORT))); + Optional.ofNullable(container.getPort()).map(Long::intValue).orElse(ContainerTrait.DEFAULT_CONTAINER_PORT))); // Need to set quarkus.container properties, otherwise these settings get overwritten by Quarkus if (container.getName() != null && !container.getName().equals(projectName)) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java index d52d13d4ab04b..de419ffe5aae0 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java @@ -29,7 +29,7 @@ public class ContainerTrait extends BaseTrait { public static final int CONTAINER_TRAIT_ORDER = 1600; - public static final long DEFAULT_CONTAINER_PORT = 8080; + public static final int DEFAULT_CONTAINER_PORT = 8080; public static final String DEFAULT_CONTAINER_PORT_NAME = "http"; public ContainerTrait() { @@ -57,7 +57,8 @@ public void apply(Traits traitConfig, TraitContext context) { if (containerTrait.getPort() != null || context.getService().isPresent() || context.getKnativeService().isPresent()) { container.addToPorts(new ContainerPortBuilder() .withName(Optional.ofNullable(containerTrait.getPortName()).orElse(DEFAULT_CONTAINER_PORT_NAME)) - .withContainerPort(Optional.ofNullable(containerTrait.getPort()).map(Long::intValue).orElse(8080)) + .withContainerPort( + Optional.ofNullable(containerTrait.getPort()).map(Long::intValue).orElse(DEFAULT_CONTAINER_PORT)) .withProtocol("TCP") .build()); } diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ServiceTrait.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ServiceTrait.java index 555a5708e9a11..ff5023492dcf3 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ServiceTrait.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ServiceTrait.java @@ -30,6 +30,7 @@ public class ServiceTrait extends BaseTrait { public static final int SERVICE_TRAIT_ORDER = 1500; + public static final int DEFAULT_SERVICE_PORT = 80; public ServiceTrait() { super("service", SERVICE_TRAIT_ORDER); @@ -46,6 +47,10 @@ public boolean configure(Traits traitConfig, TraitContext context) { return traitConfig.getService().getEnabled(); } + if (traitConfig.getContainer().getPort() != null) { + return true; + } + return TraitHelper.exposesHttpService(context); } @@ -67,7 +72,8 @@ public void apply(Traits traitConfig, TraitContext context) { .addToPorts(new ServicePortBuilder() .withName(Optional.ofNullable(containerTrait.getServicePortName()) .orElse(ContainerTrait.DEFAULT_CONTAINER_PORT_NAME)) - .withPort(Optional.ofNullable(containerTrait.getServicePort()).map(Long::intValue).orElse(80)) + .withPort(Optional.ofNullable(containerTrait.getServicePort()).map(Long::intValue) + .orElse(DEFAULT_SERVICE_PORT)) .withTargetPort( new IntOrString( Optional.ofNullable(containerTrait.getPortName()) diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelper.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelper.java index 0c7a6e87b1c58..27a7cf805cc61 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelper.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelper.java @@ -55,8 +55,6 @@ private TraitHelper() { /** * Parses given list of trait expressions to proper trait model object. * - * @param traits - * @return */ public static Traits parseTraits(String[] traits) { if (traits == null || traits.length == 0) { @@ -70,9 +68,8 @@ public static Traits parseTraits(String[] traits) { * Parses given list of trait expressions to proper trait model object. Supports trait options in the form of * key=value and trait annotation configuration. * - * @param traits trait key-value-pairs. - * @param annotations trait annotation configuration. - * @return + * @param traits trait key-value-pairs. + * @param annotations trait annotation configuration. */ public static Traits parseTraits(String[] traits, String[] annotations) { Map> traitConfigMap = new HashMap<>(); @@ -150,9 +147,6 @@ public static Traits parseTraits(String[] traits, String[] annotations) { * Resolve trait value with automatic type conversion. Some trait keys (like enabled, verbose) need to be converted * to boolean type. * - * @param traitKey - * @param value - * @return */ private static Object resolveTraitValue(String traitKey, String value) { if (traitKey.equalsIgnoreCase("enabled") || @@ -311,6 +305,13 @@ public static void configureContainerImage( containerTrait.setImage("%s%s:%s".formatted(registryPrefix, imageName, version)); } + // Plain export command always exposes a health endpoint on 8080. + // Skip this, when we decide that the health endpoint can be disabled. + if (containerTrait.getPort() == null) { + containerTrait.setPortName(ContainerTrait.DEFAULT_CONTAINER_PORT_NAME); + containerTrait.setPort((long) ContainerTrait.DEFAULT_CONTAINER_PORT); + } + traitsSpec.setContainer(containerTrait); } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java index a8f50586117fd..a918d215cfa7f 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java @@ -35,8 +35,10 @@ import io.fabric8.knative.eventing.v1.Trigger; import io.fabric8.knative.messaging.v1.Subscription; import io.fabric8.knative.sources.v1.SinkBinding; +import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.apps.Deployment; import org.apache.camel.RuntimeCamelException; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; @@ -111,7 +113,7 @@ public void shouldGenerateKubernetesManifest(RuntimeType rt) throws Exception { Assertions.assertEquals("quay.io/camel-test/route:1.0-SNAPSHOT", deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); - Assertions.assertFalse(hasService(rt)); + Assertions.assertTrue(hasService(rt)); Assertions.assertFalse(hasKnativeService(rt)); } @@ -136,7 +138,7 @@ public void shouldAddApplicationProperties(RuntimeType rt) throws Exception { Assertions.assertEquals("camel-test/route:1.0-SNAPSHOT", deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); - Assertions.assertFalse(hasService(rt)); + Assertions.assertTrue(hasService(rt)); Assertions.assertFalse(hasKnativeService(rt)); Properties applicationProperties = getApplicationProperties(workingDir); @@ -145,33 +147,64 @@ public void shouldAddApplicationProperties(RuntimeType rt) throws Exception { Assertions.assertEquals("baz", applicationProperties.get("bar")); } + @ParameterizedTest + @MethodSource("runtimeProvider") + public void shouldAddDefaultServiceSpec(RuntimeType rt) throws Exception { + KubernetesExport command = createCommand(new String[] { "classpath:route.yaml" }, + "--trait", "service.type=NodePort", + "--runtime=" + rt.runtime()); + command.doCall(); + + Assertions.assertTrue(hasService(rt)); + Assertions.assertFalse(hasKnativeService(rt)); + + Deployment deployment = getDeployment(rt); + Container container = deployment.getSpec().getTemplate().getSpec().getContainers().get(0); + Assertions.assertEquals("route", deployment.getMetadata().getName()); + Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); + Assertions.assertEquals("route:1.0-SNAPSHOT", container.getImage()); + Assertions.assertEquals(1, container.getPorts().size()); + Assertions.assertEquals("http", container.getPorts().get(0).getName()); + Assertions.assertEquals(8080, container.getPorts().get(0).getContainerPort()); + + Service service = getService(rt); + List ports = service.getSpec().getPorts(); + Assertions.assertEquals("route", service.getMetadata().getName()); + Assertions.assertEquals("NodePort", service.getSpec().getType()); + Assertions.assertEquals(1, ports.size()); + Assertions.assertEquals("http", ports.get(0).getName()); + Assertions.assertEquals(80, ports.get(0).getPort()); + Assertions.assertEquals("http", ports.get(0).getTargetPort().getStrVal()); + } + @ParameterizedTest @MethodSource("runtimeProvider") public void shouldAddServiceSpec(RuntimeType rt) throws Exception { KubernetesExport command = createCommand(new String[] { "classpath:route-service.yaml" }, - "--image-group=camel-test", "--runtime=" + rt.runtime()); + "--trait", "service.type=NodePort", + "--runtime=" + rt.runtime()); command.doCall(); Assertions.assertTrue(hasService(rt)); Assertions.assertFalse(hasKnativeService(rt)); Deployment deployment = getDeployment(rt); + Container container = deployment.getSpec().getTemplate().getSpec().getContainers().get(0); Assertions.assertEquals("route-service", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); - Assertions.assertEquals("camel-test/route-service:1.0-SNAPSHOT", - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); - Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getPorts().size()); - Assertions.assertEquals("http", - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getPorts().get(0).getName()); - Assertions.assertEquals(8080, - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getPorts().get(0).getContainerPort()); + Assertions.assertEquals("route-service:1.0-SNAPSHOT", container.getImage()); + Assertions.assertEquals(1, container.getPorts().size()); + Assertions.assertEquals("http", container.getPorts().get(0).getName()); + Assertions.assertEquals(8080, container.getPorts().get(0).getContainerPort()); Service service = getService(rt); + List ports = service.getSpec().getPorts(); Assertions.assertEquals("route-service", service.getMetadata().getName()); - Assertions.assertEquals(1, service.getSpec().getPorts().size()); - Assertions.assertEquals("http", service.getSpec().getPorts().get(0).getName()); - Assertions.assertEquals(80, service.getSpec().getPorts().get(0).getPort()); - Assertions.assertEquals("http", service.getSpec().getPorts().get(0).getTargetPort().getStrVal()); + Assertions.assertEquals("NodePort", service.getSpec().getType()); + Assertions.assertEquals(1, ports.size()); + Assertions.assertEquals("http", ports.get(0).getName()); + Assertions.assertEquals(80, ports.get(0).getPort()); + Assertions.assertEquals("http", ports.get(0).getTargetPort().getStrVal()); } @ParameterizedTest @@ -359,7 +392,7 @@ public void shouldAddKnativeBrokerSinkBinding(RuntimeType rt) throws Exception { "--image-group=camel-test", "--runtime=" + rt.runtime()); command.doCall(); - Assertions.assertFalse(hasService(rt)); + Assertions.assertTrue(hasService(rt)); Assertions.assertFalse(hasKnativeService(rt)); SinkBinding sinkBinding = getResource(rt, SinkBinding.class) @@ -399,7 +432,7 @@ public void shouldAddKnativeChannelSinkBinding(RuntimeType rt) throws Exception "--image-group=camel-test", "--runtime=" + rt.runtime()); command.doCall(); - Assertions.assertFalse(hasService(rt)); + Assertions.assertTrue(hasService(rt)); Assertions.assertFalse(hasKnativeService(rt)); SinkBinding sinkBinding = getResource(rt, SinkBinding.class) @@ -439,7 +472,7 @@ public void shouldAddKnativeEndpointSinkBinding(RuntimeType rt) throws Exception "--image-group=camel-test", "--runtime=" + rt.runtime()); command.doCall(); - Assertions.assertFalse(hasService(rt)); + Assertions.assertTrue(hasService(rt)); Assertions.assertFalse(hasKnativeService(rt)); SinkBinding sinkBinding = getResource(rt, SinkBinding.class) diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/resources/route-service.yaml b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/resources/route-service.yaml index 7dc9242ce567c..912cbf1cd2aff 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/resources/route-service.yaml +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/resources/route-service.yaml @@ -19,4 +19,4 @@ uri: 'platform-http:/roll-dice' steps: - set-body: - simple: 'roll: $simple{random(1,6)}' + simple: 'roll: $simple{random(1,7)}'