diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryClientHealthDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryClientHealthDelegate.java new file mode 100644 index 00000000..34912c2b --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryClientHealthDelegate.java @@ -0,0 +1,180 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.cloud.kubernetes.fabric8.discovery; + +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.testcontainers.k3s.K3sContainer; + +import org.springframework.boot.test.json.BasicJsonTester; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.builder; +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.retrySpec; +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.waitForLogStatement; + +/** + * @author wind57 + */ +final class Fabric8DiscoveryClientHealthDelegate { + + private Fabric8DiscoveryClientHealthDelegate() { + + } + + private static final String REACTIVE_STATUS = "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].status"; + + private static final String BLOCKING_STATUS = "$.components.discoveryComposite.components.discoveryClient.status"; + + private static final BasicJsonTester BASIC_JSON_TESTER = new BasicJsonTester( + Fabric8DiscoveryClientHealthDelegate.class); + + /** + * Reactive is disabled, only blocking is active. As such, + * KubernetesInformerDiscoveryClientAutoConfiguration::indicatorInitializer will post + * an InstanceRegisteredEvent. + * + * We assert for logs and call '/health' endpoint to see that blocking discovery + * client was initialized. + */ + static void testBlockingConfiguration(K3sContainer k3sContainer, String imageName) { + + waitForLogStatement("Will publish InstanceRegisteredEvent from blocking implementation", k3sContainer, + imageName); + waitForLogStatement("publishing InstanceRegisteredEvent", k3sContainer, imageName); + waitForLogStatement("Discovery Client has been initialized", k3sContainer, imageName); + waitForLogStatement( + "received InstanceRegisteredEvent from pod with 'app' label value : spring-cloud-kubernetes-fabric8-client-discovery", + k3sContainer, imageName); + + WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build(); + + String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class) + .retryWhen(retrySpec()).block(); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(BLOCKING_STATUS) + .isEqualTo("UP"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathArrayValue( + "$.components.discoveryComposite.components.discoveryClient.details.services") + .containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes", + "busybox-service", "external-name-service", "service-wiremock"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(REACTIVE_STATUS); + + } + + /** + * Both blocking and reactive are enabled. + */ + static void testDefaultConfiguration(K3sContainer k3sContainer, String imageName) { + + waitForLogStatement("Will publish InstanceRegisteredEvent from blocking implementation", k3sContainer, + imageName); + waitForLogStatement("publishing InstanceRegisteredEvent", k3sContainer, imageName); + waitForLogStatement("Discovery Client has been initialized", k3sContainer, imageName); + waitForLogStatement("received InstanceRegisteredEvent from pod with 'app' label value : " + + "spring-cloud-kubernetes-fabric8-client-discovery", k3sContainer, imageName); + + WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build(); + + String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class) + .retryWhen(retrySpec()).block(); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathStringValue("$.components.discoveryComposite.components.discoveryClient.status") + .isEqualTo("UP"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathArrayValue( + "$.components.discoveryComposite.components.discoveryClient.details.services") + .containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes", + "external-name-service", "service-wiremock", "busybox-service"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status").isEqualTo("UP"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue( + "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].status") + .isEqualTo("UP"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue( + "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].details.services") + .containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes", + "external-name-service", "service-wiremock", "busybox-service"); + } + + /** + * Reactive is enabled, blocking is disabled. As such, + * KubernetesInformerDiscoveryClientAutoConfiguration::indicatorInitializer will post + * an InstanceRegisteredEvent. + * + * We assert for logs and call '/health' endpoint to see that blocking discovery + * client was initialized. + */ + static void testReactiveConfiguration(K3sContainer k3sContainer, String imageName) { + + waitForLogStatement("Will publish InstanceRegisteredEvent from reactive implementation", k3sContainer, + imageName); + waitForLogStatement("publishing InstanceRegisteredEvent", k3sContainer, imageName); + waitForLogStatement("Discovery Client has been initialized", k3sContainer, imageName); + waitForLogStatement( + "received InstanceRegisteredEvent from pod with 'app' label value : spring-cloud-kubernetes-fabric8-client-discovery", + k3sContainer, imageName); + + WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build(); + + String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class) + .retryWhen(retrySpec()).block(); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status").isEqualTo("UP"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(REACTIVE_STATUS) + .isEqualTo("UP"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue( + "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].details.services") + .containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes", + "external-name-service", "service-wiremock", "busybox-service"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(BLOCKING_STATUS); + + // test for services also: + + WebClient servicesClient = builder().baseUrl("http://localhost/reactive/services").build(); + + List servicesResult = servicesClient.method(HttpMethod.GET).retrieve() + .bodyToMono(new ParameterizedTypeReference>() { + }).retryWhen(retrySpec()).block(); + + Assertions.assertThat(servicesResult).contains("spring-cloud-kubernetes-fabric8-client-discovery"); + Assertions.assertThat(servicesResult).contains("kubernetes"); + + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryClientHealthIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryClientHealthIT.java deleted file mode 100644 index 545a9e62..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryClientHealthIT.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright 2013-2023 the original author or authors. - * - * Licensed 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 - * - * https://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.springframework.cloud.kubernetes.fabric8.discovery; - -import java.io.InputStream; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import io.fabric8.kubernetes.api.model.EnvVar; -import io.fabric8.kubernetes.api.model.EnvVarBuilder; -import io.fabric8.kubernetes.api.model.Service; -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.api.model.networking.v1.Ingress; -import io.fabric8.kubernetes.client.KubernetesClient; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.Container; -import org.testcontainers.k3s.K3sContainer; -import reactor.netty.http.client.HttpClient; -import reactor.util.retry.Retry; -import reactor.util.retry.RetryBackoffSpec; - -import org.springframework.boot.test.json.BasicJsonTester; -import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; -import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; -import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * @author wind57 - */ -class Fabric8DiscoveryClientHealthIT { - - private static final String REACTIVE_STATUS = "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].status"; - - private static final String BLOCKING_STATUS = "$.components.discoveryComposite.components.discoveryClient.status"; - - private static final String NAMESPACE = "default"; - - private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery"; - - private static KubernetesClient client; - - private static final BasicJsonTester BASIC_JSON_TESTER = new BasicJsonTester(Fabric8DiscoveryClientHealthIT.class); - - private static Util util; - - private static final K3sContainer K3S = Commons.container(); - - @BeforeAll - static void beforeAll() throws Exception { - K3S.start(); - Commons.validateImage(IMAGE_NAME, K3S); - Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); - - util = new Util(K3S); - client = util.client(); - util.setUp(NAMESPACE); - } - - @AfterAll - static void after() throws Exception { - Commons.cleanUp(IMAGE_NAME, K3S); - Commons.systemPrune(); - } - - /** - * Reactive is disabled, only blocking is active. As such, - * KubernetesInformerDiscoveryClientAutoConfiguration::indicatorInitializer will post - * an InstanceRegisteredEvent. - * - * We assert for logs and call '/health' endpoint to see that blocking discovery - * client was initialized. - */ - @Test - void testBlockingConfiguration() { - - manifests(true, false, Phase.CREATE); - - assertLogStatement("Will publish InstanceRegisteredEvent from blocking implementation"); - assertLogStatement("publishing InstanceRegisteredEvent"); - assertLogStatement("Discovery Client has been initialized"); - assertLogStatement( - "received InstanceRegisteredEvent from pod with 'app' label value : spring-cloud-kubernetes-fabric8-client-discovery"); - - WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build(); - - String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class) - .retryWhen(retrySpec()).block(); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) - .extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP"); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(BLOCKING_STATUS) - .isEqualTo("UP"); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) - .extractingJsonPathArrayValue( - "$.components.discoveryComposite.components.discoveryClient.details.services") - .containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes"); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(REACTIVE_STATUS); - - manifests(true, false, Phase.DELETE); - } - - /** - * Both blocking and reactive are enabled. - */ - @Test - void testDefaultConfiguration() { - - manifests(false, false, Phase.CREATE); - - assertLogStatement("Will publish InstanceRegisteredEvent from blocking implementation"); - assertLogStatement("publishing InstanceRegisteredEvent"); - assertLogStatement("Discovery Client has been initialized"); - assertLogStatement("received InstanceRegisteredEvent from pod with 'app' label value : " - + "spring-cloud-kubernetes-fabric8-client-discovery"); - - WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build(); - - String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class) - .retryWhen(retrySpec()).block(); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) - .extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP"); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) - .extractingJsonPathStringValue("$.components.discoveryComposite.components.discoveryClient.status") - .isEqualTo("UP"); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) - .extractingJsonPathArrayValue( - "$.components.discoveryComposite.components.discoveryClient.details.services") - .containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes"); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) - .extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status").isEqualTo("UP"); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue( - "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].status") - .isEqualTo("UP"); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue( - "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].details.services") - .containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes"); - - manifests(false, false, Phase.DELETE); - } - - /** - * Reactive is enabled, blocking is disabled. As such, - * KubernetesInformerDiscoveryClientAutoConfiguration::indicatorInitializer will post - * an InstanceRegisteredEvent. - * - * We assert for logs and call '/health' endpoint to see that blocking discovery - * client was initialized. - */ - @Test - void testReactiveConfiguration() { - - manifests(false, true, Phase.CREATE); - - assertLogStatement("Will publish InstanceRegisteredEvent from reactive implementation"); - assertLogStatement("publishing InstanceRegisteredEvent"); - assertLogStatement("Discovery Client has been initialized"); - assertLogStatement( - "received InstanceRegisteredEvent from pod with 'app' label value : spring-cloud-kubernetes-fabric8-client-discovery"); - - WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build(); - - String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class) - .retryWhen(retrySpec()).block(); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)) - .extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status").isEqualTo("UP"); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(REACTIVE_STATUS) - .isEqualTo("UP"); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue( - "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].details.services") - .containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes"); - - Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(BLOCKING_STATUS); - - // test for services also: - - WebClient servicesClient = builder().baseUrl("http://localhost/reactive/services").build(); - - List servicesResult = servicesClient.method(HttpMethod.GET).retrieve() - .bodyToMono(new ParameterizedTypeReference>() { - }).retryWhen(retrySpec()).block(); - - Assertions.assertThat(servicesResult).contains("spring-cloud-kubernetes-fabric8-client-discovery"); - Assertions.assertThat(servicesResult).contains("kubernetes"); - - manifests(false, true, Phase.DELETE); - } - - private static void manifests(boolean disableReactive, boolean disableBlocking, Phase phase) { - - InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml"); - InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml"); - InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml"); - - Deployment deployment = client.apps().deployments().load(deploymentStream).get(); - List envVars = new ArrayList<>( - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv()); - - EnvVar debugLevelForCommons = new EnvVarBuilder() - .withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_DISCOVERY").withValue("DEBUG") - .build(); - - if (!disableBlocking) { - EnvVar debugBlockingEnvVar = new EnvVarBuilder() - .withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH").withValue("DEBUG") - .build(); - - EnvVar debugLevelForBlocking = new EnvVarBuilder() - .withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY").withValue("DEBUG") - .build(); - - envVars.add(debugBlockingEnvVar); - envVars.add(debugLevelForBlocking); - } - - if (!disableReactive) { - EnvVar debugReactiveEnvVar = new EnvVarBuilder() - .withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH_REACTIVE") - .withValue("DEBUG").build(); - - EnvVar debugLevelForReactive = new EnvVarBuilder() - .withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY_REACTIVE") - .withValue("DEBUG").build(); - - EnvVar debugLevelForBlocking = new EnvVarBuilder() - .withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY").withValue("DEBUG") - .build(); - - envVars.add(debugReactiveEnvVar); - envVars.add(debugLevelForBlocking); - envVars.add(debugLevelForReactive); - } - - Service service = client.services().load(serviceStream).get(); - Ingress ingress = client.network().v1().ingresses().load(ingressStream).get(); - - if (disableBlocking) { - EnvVar disableBlockingEnvVar = new EnvVarBuilder().withName("SPRING_CLOUD_DISCOVERY_BLOCKING_ENABLED") - .withValue("FALSE").build(); - envVars.add(disableBlockingEnvVar); - } - - if (disableReactive) { - EnvVar disableReactiveEnvVar = new EnvVarBuilder().withName("SPRING_CLOUD_DISCOVERY_REACTIVE_ENABLED") - .withValue("FALSE").build(); - envVars.add(disableReactiveEnvVar); - } - - envVars.add(debugLevelForCommons); - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars); - - if (phase.equals(Phase.CREATE)) { - util.createAndWait(NAMESPACE, null, deployment, service, ingress, true); - } - else { - util.deleteAndWait(NAMESPACE, deployment, service, ingress); - } - } - - private WebClient.Builder builder() { - return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); - } - - private RetryBackoffSpec retrySpec() { - return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); - } - - private void assertLogStatement(String message) { - try { - String appPodName = K3S.execInContainer("sh", "-c", - "kubectl get pods -l app=" + IMAGE_NAME + " -o=name --no-headers | tr -d '\n'").getStdout(); - - Container.ExecResult execResult = K3S.execInContainer("sh", "-c", "kubectl logs " + appPodName.trim()); - String ok = execResult.getStdout(); - Assertions.assertThat(ok).contains(message); - } - catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException(e); - } - - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryClientUtil.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryClientUtil.java new file mode 100644 index 00000000..c0a81948 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryClientUtil.java @@ -0,0 +1,289 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.cloud.kubernetes.fabric8.discovery; + +import java.time.Duration; +import java.util.Objects; + +import org.testcontainers.k3s.K3sContainer; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; + +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.awaitility.Awaitility.await; + +/** + * @author wind57 + */ +final class Fabric8DiscoveryClientUtil { + + private Fabric8DiscoveryClientUtil() { + + } + + static final String BODY_ONE = """ + { + "spec": { + "template": { + "spec": { + "containers": [{ + "name": "spring-cloud-kubernetes-fabric8-client-discovery", + "image": "image_name_here", + "env": [ + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", + "value": "DEBUG" + }, + { + "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_INCLUDEEXTERNALNAMESERVICES", + "value": "TRUE" + } + ] + }] + } + } + } + } + """; + + static final String BODY_TWO = """ + { + "spec": { + "template": { + "spec": { + "containers": [{ + "name": "spring-cloud-kubernetes-fabric8-client-discovery", + "image": "image_name_here", + "env": [ + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_DISCOVERY", + "value": "DEBUG" + }, + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH", + "value": "DEBUG" + }, + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", + "value": "DEBUG" + }, + { + "name": "SPRING_CLOUD_DISCOVERY_REACTIVE_ENABLED", + "value": "FALSE" + } + ] + }] + } + } + } + } + """; + + static final String BODY_THREE = """ + { + "spec": { + "template": { + "spec": { + "containers": [{ + "name": "spring-cloud-kubernetes-fabric8-client-discovery", + "image": "image_name_here", + "env": [ + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_DISCOVERY", + "value": "DEBUG" + }, + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH_REACTIVE", + "value": "DEBUG" + }, + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY_REACTIVE", + "value": "DEBUG" + }, + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", + "value": "DEBUG" + }, + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH", + "value": "DEBUG" + }, + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", + "value": "DEBUG" + } + ] + }] + } + } + } + } + """; + + static final String BODY_FOUR = """ + { + "spec": { + "template": { + "spec": { + "containers": [{ + "name": "spring-cloud-kubernetes-fabric8-client-discovery", + "image": "image_name_here", + "env": [ + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_DISCOVERY", + "value": "DEBUG" + }, + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH_REACTIVE", + "value": "DEBUG" + }, + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY_REACTIVE", + "value": "DEBUG" + }, + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", + "value": "DEBUG" + }, + { + "name": "SPRING_CLOUD_DISCOVERY_BLOCKING_ENABLED", + "value": "FALSE" + } + ] + }] + } + } + } + } + """; + + static final String BODY_FIVE = """ + { + "spec": { + "template": { + "spec": { + "containers": [{ + "name": "spring-cloud-kubernetes-fabric8-client-discovery", + "image": "image_name_here", + "env": [ + { + "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0", + "value": "a-uat" + }, + { + "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_1", + "value": "b-uat" + }, + { + "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_FILTER", + "value": "#root.metadata.namespace matches '^.*uat$'" + }, + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", + "value": "DEBUG" + } + ] + }] + } + } + } + } + """; + + static final String BODY_SIX = """ + { + "spec": { + "template": { + "spec": { + "containers": [{ + "name": "spring-cloud-kubernetes-fabric8-client-discovery", + "image": "image_name_here", + "env": [ + { + "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0", + "value": "a-uat" + }, + { + "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_1", + "value": "b-uat" + }, + { + "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_FILTER", + "value": "#root.metadata.namespace matches 'a-uat$'" + }, + { + "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", + "value": "DEBUG" + } + ] + }] + } + } + } + } + """; + + static final String BODY_SEVEN = """ + { + "spec": { + "template": { + "spec": { + "containers": [{ + "name": "spring-cloud-kubernetes-fabric8-client-discovery", + "image": "image_name_here", + "env": [ + { + "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0", + "value": "namespace-left" + } + ] + }] + } + } + } + } + """; + + static WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + static RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); + } + + static void waitForLogStatement(String message, K3sContainer k3sContainer, String imageName) { + try { + String appPodName = k3sContainer.execInContainer("sh", "-c", + "kubectl get pods -l app=" + imageName + " -o=name --no-headers | tr -d '\n'").getStdout(); + + await().atMost(Duration.ofMinutes(2)).pollInterval(Duration.ofSeconds(2)).until(() -> { + String execResult = k3sContainer.execInContainer("sh", "-c", "kubectl logs " + appPodName.trim()) + .getStdout(); + return execResult.contains(message); + }); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryDelegate.java new file mode 100644 index 00000000..e997d422 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryDelegate.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.cloud.kubernetes.fabric8.discovery; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; + +import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.builder; +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.retrySpec; + +/** + * @author wind57 + */ +final class Fabric8DiscoveryDelegate { + + private Fabric8DiscoveryDelegate() { + + } + + /** + * KubernetesDiscoveryClient::getServices call must include the external-name-service + * also. + */ + static void testAllServices() { + WebClient client = builder().baseUrl("http://localhost/services").build(); + + List result = client.method(HttpMethod.GET).retrieve() + .bodyToMono(new ParameterizedTypeReference>() { + + }).retryWhen(retrySpec()).block(); + + Assertions.assertEquals(result.size(), 5); + Assertions.assertTrue(result.contains("kubernetes")); + Assertions.assertTrue(result.contains("spring-cloud-kubernetes-fabric8-client-discovery")); + Assertions.assertTrue(result.contains("service-wiremock")); + Assertions.assertTrue(result.contains("busybox-service")); + Assertions.assertTrue(result.contains("external-name-service")); + } + + static void testExternalNameServiceInstance() { + + WebClient client = builder().baseUrl("http://localhost/service-instances/external-name-service").build(); + List serviceInstances = client.method(HttpMethod.GET).retrieve() + .bodyToMono(new ParameterizedTypeReference>() { + + }).retryWhen(retrySpec()).block(); + + DefaultKubernetesServiceInstance result = serviceInstances.get(0); + + Assertions.assertEquals(serviceInstances.size(), 1); + Assertions.assertEquals(result.getServiceId(), "external-name-service"); + Assertions.assertNotNull(result.getInstanceId()); + Assertions.assertEquals(result.getHost(), "spring.io"); + Assertions.assertEquals(result.getPort(), -1); + Assertions.assertEquals(result.getMetadata(), Map.of("k8s_namespace", "default", "type", "ExternalName")); + Assertions.assertFalse(result.isSecure()); + Assertions.assertEquals(result.getUri().toASCIIString(), "spring.io"); + Assertions.assertEquals(result.getScheme(), "http"); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryFilterDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryFilterDelegate.java new file mode 100644 index 00000000..51a40692 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryFilterDelegate.java @@ -0,0 +1,127 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.cloud.kubernetes.fabric8.discovery; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; + +import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.builder; +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.retrySpec; + +final class Fabric8DiscoveryFilterDelegate { + + private Fabric8DiscoveryFilterDelegate() { + + } + + /** + *
+	 *     - service "wiremock" is present in namespace "a-uat"
+	 *     - service "wiremock" is present in namespace "b-uat"
+	 *
+	 *     - we search with a predicate : "#root.metadata.namespace matches '^uat.*$'"
+	 *
+	 *     As such, both services are found via 'getInstances' call.
+	 * 
+ */ + static void filterMatchesBothNamespacesViaThePredicate() { + + WebClient clientServices = builder().baseUrl("http://localhost/services").build(); + + @SuppressWarnings("unchecked") + List services = (List) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class) + .retryWhen(retrySpec()).block(); + + Assertions.assertEquals(services.size(), 1); + Assertions.assertTrue(services.contains("service-wiremock")); + + WebClient client = builder().baseUrl("http://localhost/service-instances/service-wiremock").build(); + List serviceInstances = client.method(HttpMethod.GET).retrieve() + .bodyToMono(new ParameterizedTypeReference>() { + + }).retryWhen(retrySpec()).block(); + + Assertions.assertEquals(serviceInstances.size(), 2); + List sorted = serviceInstances.stream() + .sorted(Comparator.comparing(DefaultKubernetesServiceInstance::getNamespace)).toList(); + + DefaultKubernetesServiceInstance first = sorted.get(0); + Assertions.assertEquals(first.getServiceId(), "service-wiremock"); + Assertions.assertNotNull(first.getInstanceId()); + Assertions.assertEquals(first.getPort(), 8080); + Assertions.assertEquals(first.getNamespace(), "a-uat"); + Assertions.assertEquals(first.getMetadata(), + Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP")); + + DefaultKubernetesServiceInstance second = sorted.get(1); + Assertions.assertEquals(second.getServiceId(), "service-wiremock"); + Assertions.assertNotNull(second.getInstanceId()); + Assertions.assertEquals(second.getPort(), 8080); + Assertions.assertEquals(second.getNamespace(), "b-uat"); + Assertions.assertEquals(second.getMetadata(), + Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "b-uat", "type", "ClusterIP")); + + } + + /** + *
+	 *     - service "wiremock" is present in namespace "a-uat"
+	 *     - service "wiremock" is present in namespace "b-uat"
+	 *
+	 *     - we search with a predicate : "#root.metadata.namespace matches 'a-uat$'"
+	 *
+	 *     As such, only service from 'a-uat' namespace matches.
+	 * 
+ */ + static void filterMatchesOneNamespaceViaThePredicate() { + + WebClient clientServices = builder().baseUrl("http://localhost/services").build(); + + @SuppressWarnings("unchecked") + List services = (List) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class) + .retryWhen(retrySpec()).block(); + + Assertions.assertEquals(services.size(), 1); + Assertions.assertTrue(services.contains("service-wiremock")); + + WebClient client = builder().baseUrl("http://localhost/service-instances/service-wiremock").build(); + List serviceInstances = client.method(HttpMethod.GET).retrieve() + .bodyToMono(new ParameterizedTypeReference>() { + + }).retryWhen(retrySpec()).block(); + + Assertions.assertEquals(serviceInstances.size(), 1); + + DefaultKubernetesServiceInstance first = serviceInstances.get(0); + Assertions.assertEquals(first.getServiceId(), "service-wiremock"); + Assertions.assertNotNull(first.getInstanceId()); + Assertions.assertEquals(first.getPort(), 8080); + Assertions.assertEquals(first.getNamespace(), "a-uat"); + Assertions.assertEquals(first.getMetadata(), + Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP")); + + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryFilterIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryFilterIT.java deleted file mode 100644 index df958c16..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryFilterIT.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright 2013-2023 the original author or authors. - * - * Licensed 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 - * - * https://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.springframework.cloud.kubernetes.fabric8.discovery; - -import java.io.InputStream; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import io.fabric8.kubernetes.api.model.EnvVar; -import io.fabric8.kubernetes.api.model.EnvVarBuilder; -import io.fabric8.kubernetes.api.model.Service; -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.api.model.networking.v1.Ingress; -import io.fabric8.kubernetes.client.KubernetesClient; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.testcontainers.k3s.K3sContainer; -import reactor.netty.http.client.HttpClient; -import reactor.util.retry.Retry; -import reactor.util.retry.RetryBackoffSpec; - -import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance; -import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; -import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; -import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.client.WebClient; - -class Fabric8DiscoveryFilterIT { - - private static final String FILTER_BOTH_NAMESPACES = "#root.metadata.namespace matches '^.*uat$'"; - - private static final String FILTER_SINGLE_NAMESPACE = "#root.metadata.namespace matches 'a-uat$'"; - - private static final String NAMESPACE_A_UAT = "a-uat"; - - private static final String NAMESPACE_B_UAT = "b-uat"; - - private static final String NAMESPACE = "default"; - - private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery"; - - private static KubernetesClient client; - - private static Util util; - - private static final K3sContainer K3S = Commons.container(); - - @BeforeAll - static void beforeAll() throws Exception { - K3S.start(); - Commons.validateImage(IMAGE_NAME, K3S); - Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); - - util = new Util(K3S); - client = util.client(); - util.setUp(NAMESPACE); - } - - @AfterAll - static void afterAll() { - Commons.systemPrune(); - } - - @BeforeEach - void beforeEach() { - util.createNamespace(NAMESPACE_A_UAT); - util.createNamespace(NAMESPACE_B_UAT); - util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.CREATE); - util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.CREATE); - } - - @AfterEach - void afterEach() { - util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.DELETE); - util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.DELETE); - util.deleteNamespace(NAMESPACE_A_UAT); - util.deleteNamespace(NAMESPACE_B_UAT); - } - - @AfterAll - static void after() throws Exception { - Commons.cleanUp(IMAGE_NAME, K3S); - } - - /** - *
-	 *     - service "wiremock" is present in namespace "a-uat"
-	 *     - service "wiremock" is present in namespace "b-uat"
-	 *
-	 *     - we search with a predicate : "#root.metadata.namespace matches '^uat.*$'"
-	 *
-	 *     As such, both services are found via 'getInstances' call.
-	 * 
- */ - @Test - void filterMatchesBothNamespacesViaThePredicate() { - - manifests(Phase.CREATE, FILTER_BOTH_NAMESPACES); - - WebClient clientServices = builder().baseUrl("http://localhost/services").build(); - - @SuppressWarnings("unchecked") - List services = (List) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class) - .retryWhen(retrySpec()).block(); - - Assertions.assertEquals(services.size(), 1); - Assertions.assertTrue(services.contains("service-wiremock")); - - WebClient client = builder().baseUrl("http://localhost/service-instances/service-wiremock").build(); - List serviceInstances = client.method(HttpMethod.GET).retrieve() - .bodyToMono(new ParameterizedTypeReference>() { - - }).retryWhen(retrySpec()).block(); - - Assertions.assertEquals(serviceInstances.size(), 2); - List sorted = serviceInstances.stream() - .sorted(Comparator.comparing(DefaultKubernetesServiceInstance::getNamespace)).toList(); - - DefaultKubernetesServiceInstance first = sorted.get(0); - Assertions.assertEquals(first.getServiceId(), "service-wiremock"); - Assertions.assertNotNull(first.getInstanceId()); - Assertions.assertEquals(first.getPort(), 8080); - Assertions.assertEquals(first.getNamespace(), "a-uat"); - Assertions.assertEquals(first.getMetadata(), - Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP")); - - DefaultKubernetesServiceInstance second = sorted.get(1); - Assertions.assertEquals(second.getServiceId(), "service-wiremock"); - Assertions.assertNotNull(second.getInstanceId()); - Assertions.assertEquals(second.getPort(), 8080); - Assertions.assertEquals(second.getNamespace(), "b-uat"); - Assertions.assertEquals(second.getMetadata(), - Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "b-uat", "type", "ClusterIP")); - - manifests(Phase.DELETE, FILTER_BOTH_NAMESPACES); - } - - /** - *
-	 *     - service "wiremock" is present in namespace "a-uat"
-	 *     - service "wiremock" is present in namespace "b-uat"
-	 *
-	 *     - we search with a predicate : "#root.metadata.namespace matches 'a-uat$'"
-	 *
-	 *     As such, only service from 'a-uat' namespace matches.
-	 * 
- */ - @Test - void filterMatchesOneNamespaceViaThePredicate() { - manifests(Phase.CREATE, FILTER_SINGLE_NAMESPACE); - - WebClient clientServices = builder().baseUrl("http://localhost/services").build(); - - @SuppressWarnings("unchecked") - List services = (List) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class) - .retryWhen(retrySpec()).block(); - - Assertions.assertEquals(services.size(), 1); - Assertions.assertTrue(services.contains("service-wiremock")); - - WebClient client = builder().baseUrl("http://localhost/service-instances/service-wiremock").build(); - List serviceInstances = client.method(HttpMethod.GET).retrieve() - .bodyToMono(new ParameterizedTypeReference>() { - - }).retryWhen(retrySpec()).block(); - - Assertions.assertEquals(serviceInstances.size(), 1); - - DefaultKubernetesServiceInstance first = serviceInstances.get(0); - Assertions.assertEquals(first.getServiceId(), "service-wiremock"); - Assertions.assertNotNull(first.getInstanceId()); - Assertions.assertEquals(first.getPort(), 8080); - Assertions.assertEquals(first.getNamespace(), "a-uat"); - Assertions.assertEquals(first.getMetadata(), - Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP")); - - manifests(Phase.DELETE, FILTER_SINGLE_NAMESPACE); - } - - private static void manifests(Phase phase, String serviceFilter) { - - InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml"); - InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml"); - InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml"); - - Deployment deployment = client.apps().deployments().load(deploymentStream).get(); - List envVars = new ArrayList<>( - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv()); - EnvVar namespaceAUat = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0") - .withValue(NAMESPACE_A_UAT).build(); - EnvVar namespaceBUat = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_1") - .withValue(NAMESPACE_B_UAT).build(); - EnvVar filter = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_FILTER") - .withValue(serviceFilter).build(); - EnvVar debug = new EnvVarBuilder() - .withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY").withValue("DEBUG") - .build(); - envVars.add(namespaceAUat); - envVars.add(namespaceBUat); - envVars.add(filter); - envVars.add(debug); - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars); - - Service service = client.services().load(serviceStream).get(); - Ingress ingress = client.network().v1().ingresses().load(ingressStream).get(); - - if (phase.equals(Phase.CREATE)) { - client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get()) - .create(); - util.createAndWait(NAMESPACE, null, deployment, service, ingress, true); - } - else { - client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get()) - .delete(); - util.deleteAndWait(NAMESPACE, deployment, service, ingress); - } - - } - - private static InputStream getAdminRole() { - return util.inputStream("namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml"); - } - - private WebClient.Builder builder() { - return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); - } - - private RetryBackoffSpec retrySpec() { - return Retry.fixedDelay(15, Duration.ofSeconds(2)).filter(Objects::nonNull); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryIT.java deleted file mode 100644 index a82033fd..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryIT.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed 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 - * - * https://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.springframework.cloud.kubernetes.fabric8.discovery; - -import java.io.InputStream; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import io.fabric8.kubernetes.api.model.EnvVar; -import io.fabric8.kubernetes.api.model.EnvVarBuilder; -import io.fabric8.kubernetes.api.model.Service; -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.api.model.networking.v1.Ingress; -import io.fabric8.kubernetes.client.KubernetesClient; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.testcontainers.k3s.K3sContainer; -import reactor.netty.http.client.HttpClient; -import reactor.util.retry.Retry; -import reactor.util.retry.RetryBackoffSpec; - -import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance; -import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; -import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; -import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * @author wind57 - */ -class Fabric8DiscoveryIT { - - private static final String NAMESPACE = "default"; - - private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery"; - - private static KubernetesClient client; - - private static Util util; - - private static final K3sContainer K3S = Commons.container(); - - @BeforeAll - static void beforeAll() throws Exception { - K3S.start(); - Commons.validateImage(IMAGE_NAME, K3S); - Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); - - util = new Util(K3S); - client = util.client(); - - util.setUp(NAMESPACE); - - manifests(Phase.CREATE); - util.wiremock(NAMESPACE, "/wiremock", Phase.CREATE); - } - - @AfterAll - static void after() throws Exception { - util.wiremock(NAMESPACE, "/wiremock", Phase.DELETE); - manifests(Phase.DELETE); - Commons.cleanUp(IMAGE_NAME, K3S); - Commons.systemPrune(); - } - - /** - * KubernetesDiscoveryClient::getServices call must include the external-name-service - * also. - */ - @Test - void testAllServices() { - WebClient client = builder().baseUrl("http://localhost/services").build(); - - List result = client.method(HttpMethod.GET).retrieve() - .bodyToMono(new ParameterizedTypeReference>() { - - }).retryWhen(retrySpec()).block(); - - Assertions.assertEquals(result.size(), 4); - Assertions.assertTrue(result.contains("kubernetes")); - Assertions.assertTrue(result.contains("spring-cloud-kubernetes-fabric8-client-discovery")); - Assertions.assertTrue(result.contains("service-wiremock")); - Assertions.assertTrue(result.contains("external-name-service")); - } - - @Test - void testExternalNameServiceInstance() { - - WebClient client = builder().baseUrl("http://localhost/service-instances/external-name-service").build(); - List serviceInstances = client.method(HttpMethod.GET).retrieve() - .bodyToMono(new ParameterizedTypeReference>() { - - }).retryWhen(retrySpec()).block(); - - DefaultKubernetesServiceInstance result = serviceInstances.get(0); - - Assertions.assertEquals(serviceInstances.size(), 1); - Assertions.assertEquals(result.getServiceId(), "external-name-service"); - Assertions.assertNotNull(result.getInstanceId()); - Assertions.assertEquals(result.getHost(), "spring.io"); - Assertions.assertEquals(result.getPort(), -1); - Assertions.assertEquals(result.getMetadata(), Map.of("k8s_namespace", "default", "type", "ExternalName")); - Assertions.assertFalse(result.isSecure()); - Assertions.assertEquals(result.getUri().toASCIIString(), "spring.io"); - Assertions.assertEquals(result.getScheme(), "http"); - } - - private static void manifests(Phase phase) { - - InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml"); - InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml"); - InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml"); - InputStream externalNameServiceInputStream = util.inputStream("external-name-service.yaml"); - - Deployment deployment = client.apps().deployments().load(deploymentStream).get(); - - List existing = new ArrayList<>( - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv()); - existing.add(new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_INCLUDEEXTERNALNAMESERVICES") - .withValue("true").build()); - existing.add( - new EnvVarBuilder().withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY") - .withValue("DEBUG").build()); - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(existing); - - Service service = client.services().load(serviceStream).get(); - Service externalNameService = client.services().load(externalNameServiceInputStream).get(); - Ingress ingress = client.network().v1().ingresses().load(ingressStream).get(); - - if (phase.equals(Phase.CREATE)) { - util.createAndWait(NAMESPACE, null, deployment, service, ingress, true); - util.createAndWait(NAMESPACE, null, null, externalNameService, null, false); - } - else { - util.deleteAndWait(NAMESPACE, deployment, service, ingress); - util.deleteAndWait(NAMESPACE, null, externalNameService, null); - } - - } - - private WebClient.Builder builder() { - return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); - } - - private RetryBackoffSpec retrySpec() { - return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryNamespaceDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryNamespaceDelegate.java new file mode 100644 index 00000000..70e90c0c --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryNamespaceDelegate.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.cloud.kubernetes.fabric8.discovery; + +import java.util.List; + +import io.fabric8.kubernetes.api.model.Endpoints; +import org.junit.jupiter.api.Assertions; + +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.builder; +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.retrySpec; + +/** + * @author mbialkowski1 + */ +final class Fabric8DiscoveryNamespaceDelegate { + + private Fabric8DiscoveryNamespaceDelegate() { + + } + + static void namespaceFilter() { + WebClient clientServices = builder().baseUrl("http://localhost/services").build(); + + @SuppressWarnings("unchecked") + List services = (List) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class) + .retryWhen(retrySpec()).block(); + + Assertions.assertEquals(services.size(), 1); + Assertions.assertTrue(services.contains("service-wiremock")); + + WebClient clientEndpoints = builder().baseUrl("http://localhost/endpoints/service-wiremock").build(); + + List endpoints = clientEndpoints.method(HttpMethod.GET).retrieve() + .bodyToMono(new ParameterizedTypeReference>() { + }).retryWhen(retrySpec()).block(); + + Assertions.assertEquals(endpoints.size(), 1); + Assertions.assertEquals(endpoints.get(0).getMetadata().getNamespace(), "namespace-left"); + + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryNamespaceFilterIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryNamespaceFilterIT.java deleted file mode 100644 index 3cc03d2f..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryNamespaceFilterIT.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed 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 - * - * https://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.springframework.cloud.kubernetes.fabric8.discovery; - -import java.io.InputStream; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import io.fabric8.kubernetes.api.model.Endpoints; -import io.fabric8.kubernetes.api.model.EnvVar; -import io.fabric8.kubernetes.api.model.EnvVarBuilder; -import io.fabric8.kubernetes.api.model.Service; -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.api.model.networking.v1.Ingress; -import io.fabric8.kubernetes.client.KubernetesClient; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.testcontainers.k3s.K3sContainer; -import reactor.netty.http.client.HttpClient; -import reactor.util.retry.Retry; -import reactor.util.retry.RetryBackoffSpec; - -import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; -import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; -import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * @author mbialkowski1 - */ -class Fabric8DiscoveryNamespaceFilterIT { - - private static final String NAMESPACE = "default"; - - private static final String NAMESPACE_LEFT = "namespace-left"; - - private static final String NAMESPACE_RIGHT = "namespace-right"; - - private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery"; - - private static Util util; - - private static KubernetesClient client; - - private static final K3sContainer K3S = Commons.container(); - - @BeforeAll - static void beforeAll() throws Exception { - K3S.start(); - Commons.validateImage(IMAGE_NAME, K3S); - Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); - - util = new Util(K3S); - client = util.client(); - util.setUp(NAMESPACE); - - manifests(Phase.CREATE); - util.createNamespace(NAMESPACE_LEFT); - util.createNamespace(NAMESPACE_RIGHT); - util.wiremock(NAMESPACE_LEFT, "/wiremock", Phase.CREATE); - util.wiremock(NAMESPACE_RIGHT, "/wiremock", Phase.CREATE); - } - - @AfterAll - static void after() throws Exception { - manifests(Phase.DELETE); - util.wiremock(NAMESPACE_LEFT, "/wiremock", Phase.DELETE); - util.wiremock(NAMESPACE_RIGHT, "/wiremock", Phase.DELETE); - util.deleteNamespace(NAMESPACE_LEFT); - util.deleteNamespace(NAMESPACE_RIGHT); - Commons.cleanUp(IMAGE_NAME, K3S); - Commons.systemPrune(); - } - - @Test - void test() { - WebClient clientServices = builder().baseUrl("http://localhost/services").build(); - - @SuppressWarnings("unchecked") - List services = (List) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class) - .retryWhen(retrySpec()).block(); - - Assertions.assertEquals(services.size(), 1); - Assertions.assertTrue(services.contains("service-wiremock")); - - WebClient clientEndpoints = builder().baseUrl("http://localhost/endpoints/service-wiremock").build(); - - List endpoints = clientEndpoints.method(HttpMethod.GET).retrieve() - .bodyToMono(new ParameterizedTypeReference>() { - }).retryWhen(retrySpec()).block(); - - Assertions.assertEquals(endpoints.size(), 1); - Assertions.assertEquals(endpoints.get(0).getMetadata().getNamespace(), NAMESPACE_LEFT); - - } - - private static void manifests(Phase phase) { - - InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml"); - InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml"); - InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml"); - - Deployment deployment = client.apps().deployments().load(deploymentStream).get(); - List envVars = new ArrayList<>( - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv()); - EnvVar activeProfileProperty = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0") - .withValue(NAMESPACE_LEFT).build(); - envVars.add(activeProfileProperty); - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars); - - Service service = client.services().load(serviceStream).get(); - Ingress ingress = client.network().v1().ingresses().load(ingressStream).get(); - - if (phase.equals(Phase.CREATE)) { - client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get()) - .create(); - util.createAndWait(NAMESPACE, null, deployment, service, ingress, true); - } - else { - client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get()) - .delete(); - util.deleteAndWait(NAMESPACE, deployment, service, ingress); - } - - } - - private static InputStream getAdminRole() { - return util.inputStream("namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml"); - } - - private WebClient.Builder builder() { - return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); - } - - private RetryBackoffSpec retrySpec() { - return Retry.fixedDelay(15, Duration.ofSeconds(2)).filter(Objects::nonNull); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryPodMetadataIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryPodMetadataIT.java index 1c37d380..6f7c463d 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryPodMetadataIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8DiscoveryPodMetadataIT.java @@ -47,15 +47,36 @@ import org.springframework.http.HttpMethod; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_FIVE; +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_FOUR; +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_ONE; +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_SEVEN; +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_SIX; +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_THREE; +import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_TWO; +import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.pomVersion; + /** * @author wind57 */ class Fabric8DiscoveryPodMetadataIT { + private static final String DEPLOYMENT_NAME = "spring-cloud-kubernetes-fabric8-client-discovery-deployment"; + private static final String NAMESPACE = "default"; + private static final String NAMESPACE_A_UAT = "a-uat"; + + private static final String NAMESPACE_B_UAT = "b-uat"; + + private static final String NAMESPACE_LEFT = "namespace-left"; + + private static final String NAMESPACE_RIGHT = "namespace-right"; + private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery"; + private static final String DOCKER_IMAGE = "docker.io/springcloud/" + IMAGE_NAME + ":" + pomVersion(); + private static KubernetesClient client; private static Util util; @@ -74,12 +95,35 @@ class Fabric8DiscoveryPodMetadataIT { util.setUp(NAMESPACE); manifests(Phase.CREATE); + util.wiremock(NAMESPACE, "/wiremock", Phase.CREATE); util.busybox(NAMESPACE, Phase.CREATE); + + util.createNamespace(NAMESPACE_A_UAT); + util.createNamespace(NAMESPACE_B_UAT); + util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.CREATE); + util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.CREATE); + + util.createNamespace(NAMESPACE_LEFT); + util.createNamespace(NAMESPACE_RIGHT); + util.wiremock(NAMESPACE_LEFT, "/wiremock", Phase.CREATE); + util.wiremock(NAMESPACE_RIGHT, "/wiremock", Phase.CREATE); } @AfterAll static void after() throws Exception { + util.wiremock(NAMESPACE, "/wiremock", Phase.DELETE); util.busybox(NAMESPACE, Phase.DELETE); + + util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.DELETE); + util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.DELETE); + util.deleteNamespace(NAMESPACE_A_UAT); + util.deleteNamespace(NAMESPACE_B_UAT); + + util.wiremock(NAMESPACE_LEFT, "/wiremock", Phase.DELETE); + util.wiremock(NAMESPACE_RIGHT, "/wiremock", Phase.DELETE); + util.deleteNamespace(NAMESPACE_LEFT); + util.deleteNamespace(NAMESPACE_RIGHT); + manifests(Phase.DELETE); Commons.cleanUp(IMAGE_NAME, K3S); Commons.systemPrune(); @@ -123,12 +167,65 @@ class Fabric8DiscoveryPodMetadataIT { Map.of("k8s_namespace", "default", "type", "ClusterIP", "port.busybox-port", "80")); Assertions.assertTrue(withCustomAnnotation.podMetadata().get("annotations").entrySet().stream().anyMatch( x -> x.getKey().equals("custom-annotation") && x.getValue().equals("custom-annotation-value"))); + + testAllOther(); + } + + private void testAllOther() { + testAllServices(); + testExternalNameServiceInstance(); + testBlockingConfiguration(); + testDefaultConfiguration(); + testReactiveConfiguration(); + filterMatchesBothNamespacesViaThePredicate(); + filterMatchesOneNamespaceViaThePredicate(); + namespaceFilter(); + } + + private void testAllServices() { + util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_ONE, Map.of("app", IMAGE_NAME)); + Fabric8DiscoveryDelegate.testAllServices(); + } + + private void testExternalNameServiceInstance() { + Fabric8DiscoveryDelegate.testExternalNameServiceInstance(); + } + + private void testBlockingConfiguration() { + util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_TWO, Map.of("app", IMAGE_NAME)); + Fabric8DiscoveryClientHealthDelegate.testBlockingConfiguration(K3S, IMAGE_NAME); + } + + private void testDefaultConfiguration() { + util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_THREE, Map.of("app", IMAGE_NAME)); + Fabric8DiscoveryClientHealthDelegate.testDefaultConfiguration(K3S, IMAGE_NAME); + } + + private void testReactiveConfiguration() { + util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_FOUR, Map.of("app", IMAGE_NAME)); + Fabric8DiscoveryClientHealthDelegate.testReactiveConfiguration(K3S, IMAGE_NAME); + } + + private void filterMatchesBothNamespacesViaThePredicate() { + util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_FIVE, Map.of("app", IMAGE_NAME)); + Fabric8DiscoveryFilterDelegate.filterMatchesBothNamespacesViaThePredicate(); + } + + private void filterMatchesOneNamespaceViaThePredicate() { + util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_SIX, Map.of("app", IMAGE_NAME)); + Fabric8DiscoveryFilterDelegate.filterMatchesOneNamespaceViaThePredicate(); + } + + private void namespaceFilter() { + util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_SEVEN, Map.of("app", IMAGE_NAME)); + Fabric8DiscoveryNamespaceDelegate.namespaceFilter(); } private static void manifests(Phase phase) { InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml"); - InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml"); + InputStream externalNameServiceStream = util.inputStream("external-name-service.yaml"); + InputStream discoveryServiceStream = util.inputStream("fabric8-discovery-service.yaml"); InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml"); Deployment deployment = client.apps().deployments().load(deploymentStream).get(); @@ -144,18 +241,29 @@ class Fabric8DiscoveryPodMetadataIT { .withValue("DEBUG").build()); deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(existing); - Service service = client.services().load(serviceStream).get(); + Service externalServiceName = client.services().load(externalNameServiceStream).get(); + Service discoveryService = client.services().load(discoveryServiceStream).get(); Ingress ingress = client.network().v1().ingresses().load(ingressStream).get(); if (phase.equals(Phase.CREATE)) { - util.createAndWait(NAMESPACE, null, deployment, service, ingress, true); + client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get()) + .create(); + util.createAndWait(NAMESPACE, IMAGE_NAME, deployment, discoveryService, ingress, true); + util.createAndWait(NAMESPACE, null, null, externalServiceName, null, true); } else { - util.deleteAndWait(NAMESPACE, deployment, service, ingress); + client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get()) + .delete(); + util.deleteAndWait(NAMESPACE, deployment, discoveryService, ingress); + util.deleteAndWait(NAMESPACE, null, externalServiceName, null); } } + private static InputStream getAdminRole() { + return util.inputStream("namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml"); + } + private WebClient.Builder builder() { return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); } diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/fabric8_client/Util.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/fabric8_client/Util.java index 748f6203..85a04deb 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/fabric8_client/Util.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/fabric8_client/Util.java @@ -20,6 +20,7 @@ import java.io.InputStream; import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -31,6 +32,7 @@ import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceAccount; import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.apps.DeploymentList; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.rbac.ClusterRole; import io.fabric8.kubernetes.api.model.rbac.Role; @@ -38,6 +40,8 @@ import io.fabric8.kubernetes.api.model.rbac.RoleBinding; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.dsl.base.PatchContext; +import io.fabric8.kubernetes.client.dsl.base.PatchType; import jakarta.annotation.Nullable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -46,6 +50,7 @@ import org.testcontainers.k3s.K3sContainer; import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; import static org.awaitility.Awaitility.await; +import static org.junit.Assert.fail; import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.loadImage; import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.pomVersion; import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.pullImage; @@ -366,6 +371,50 @@ public final class Util { } + public void patchWithReplace(String imageName, String deploymentName, String namespace, String patchBody, + Map labels) { + String body = patchBody.replace("image_name_here", imageName); + + client.apps().deployments().inNamespace(namespace).withName(deploymentName) + .patch(PatchContext.of(PatchType.JSON_MERGE), body); + + waitForDeploymentAfterPatch(deploymentName, namespace, labels); + } + + private void waitForDeploymentAfterPatch(String deploymentName, String namespace, Map labels) { + try { + await().pollDelay(Duration.ofSeconds(4)).pollInterval(Duration.ofSeconds(3)).atMost(60, TimeUnit.SECONDS) + .until(() -> isDeploymentReadyAfterPatch(deploymentName, namespace, labels)); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + } + + private boolean isDeploymentReadyAfterPatch(String deploymentName, String namespace, Map labels) { + + DeploymentList deployments = client.apps().deployments().inNamespace(namespace).list(); + + if (deployments.getItems().isEmpty()) { + fail("No deployment with name " + deploymentName); + } + + Deployment deployment = deployments.getItems().get(0); + // if no replicas are defined, it means only 1 is needed + int replicas = Optional.ofNullable(deployment.getSpec().getReplicas()).orElse(1); + + int numberOfPods = client.pods().inNamespace(namespace).withLabels(labels).list().getItems().size(); + + if (numberOfPods != replicas) { + LOG.info("number of pods not yet stabilized"); + return false; + } + + return replicas == Optional.ofNullable(deployment.getStatus().getReadyReplicas()).orElse(0); + + } + private void innerSetup(String namespace, InputStream serviceAccountAsStream, InputStream roleBindingAsStream, InputStream roleAsStream) { ServiceAccount serviceAccountFromStream = client.serviceAccounts().load(serviceAccountAsStream).get(); diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/native_client/Util.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/native_client/Util.java index e46aa618..0dc5efee 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/native_client/Util.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/native_client/Util.java @@ -610,7 +610,7 @@ public final class Util { V1DeploymentList deployments = appsV1Api.listNamespacedDeployment(namespace, null, null, null, "metadata.name=" + deploymentName, null, null, null, null, null, null); - if (deployments.getItems().size() < 1) { + if (deployments.getItems().isEmpty()) { fail("No deployment with name " + deploymentName); }