diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml index 33692dce..ed7a0a63 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml @@ -50,6 +50,21 @@ true + + + + org.springframework.boot + spring-boot-maven-plugin + + + build-image + + true + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8ApplicationDiscoveryListener.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8ApplicationDiscoveryListener.java deleted file mode 100644 index 7ecafcef..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8ApplicationDiscoveryListener.java +++ /dev/null @@ -1,45 +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.client.discovery; - -import io.fabric8.kubernetes.api.model.Pod; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; -import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryClientHealthIndicatorInitializer; -import org.springframework.context.ApplicationListener; -import org.springframework.core.log.LogAccessor; -import org.springframework.stereotype.Component; - -/** - * @author wind57 - */ -@Component -public class Fabric8ApplicationDiscoveryListener implements ApplicationListener> { - - private static final LogAccessor LOG = new LogAccessor( - LogFactory.getLog(Fabric8ApplicationDiscoveryListener.class)); - - @Override - public void onApplicationEvent(InstanceRegisteredEvent event) { - Pod pod = (Pod) ((KubernetesDiscoveryClientHealthIndicatorInitializer.RegisteredEventSource) event.getSource()) - .pod(); - LOG.info(() -> "received InstanceRegisteredEvent from pod with 'app' label value : " - + pod.getMetadata().getLabels().get("app")); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryApp.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryApp.java index 1ef7ea1a..751b2b8e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryApp.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryApp.java @@ -23,7 +23,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; * @author wind57 */ @SpringBootApplication -public class Fabric8DiscoveryApp { +class Fabric8DiscoveryApp { public static void main(String[] args) { SpringApplication.run(Fabric8DiscoveryApp.class, args); diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryController.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryController.java deleted file mode 100644 index 898c0ebf..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryController.java +++ /dev/null @@ -1,59 +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.client.discovery; - -import java.util.List; - -import io.fabric8.kubernetes.api.model.Endpoints; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesDiscoveryClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author wind57 - */ -@RestController -public class Fabric8DiscoveryController { - - private final KubernetesDiscoveryClient discoveryClient; - - public Fabric8DiscoveryController(ObjectProvider discoveryClient) { - KubernetesDiscoveryClient[] local = new KubernetesDiscoveryClient[1]; - discoveryClient.ifAvailable(x -> local[0] = x); - this.discoveryClient = local[0]; - } - - @GetMapping("/services") - public List allServices() { - return discoveryClient.getServices(); - } - - @GetMapping("/endpoints/{serviceId}") - public List getEndPointsList(@PathVariable("serviceId") String serviceId) { - return discoveryClient.getEndPointsList(serviceId); - } - - @GetMapping("/service-instances/{serviceId}") - public List serviceInstances(@PathVariable("serviceId") String serviceId) { - return discoveryClient.getInstances(serviceId); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8ReactiveDiscoveryController.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8ReactiveDiscoveryController.java deleted file mode 100644 index d73cdfa6..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8ReactiveDiscoveryController.java +++ /dev/null @@ -1,55 +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.client.discovery; - -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.kubernetes.fabric8.discovery.reactive.KubernetesReactiveDiscoveryClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author wind57 - */ -@RestController -public class Fabric8ReactiveDiscoveryController { - - private final KubernetesReactiveDiscoveryClient reactiveDiscoveryClient; - - public Fabric8ReactiveDiscoveryController( - ObjectProvider reactiveDiscoveryClient) { - KubernetesReactiveDiscoveryClient[] local = new KubernetesReactiveDiscoveryClient[1]; - reactiveDiscoveryClient.ifAvailable(x -> local[0] = x); - this.reactiveDiscoveryClient = local[0]; - } - - @GetMapping("/reactive/services") - public Mono> allServices() { - return reactiveDiscoveryClient.getServices().collectList(); - } - - @GetMapping("/reactive/service-instances/{serviceId}") - public Mono> serviceInstances(@PathVariable("serviceId") String serviceId) { - return reactiveDiscoveryClient.getInstances(serviceId).collectList(); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryAllServicesIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryAllServicesIT.java new file mode 100644 index 00000000..ef5d34fa --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryAllServicesIT.java @@ -0,0 +1,126 @@ +/* + * Copyright 2012-2024 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.client.discovery; + +import java.io.InputStream; + +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.client.utils.Serialization; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.kubernetes.integration.tests.commons.Images; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.test.context.TestPropertySource; + +import static org.springframework.cloud.kubernetes.fabric8.client.discovery.TestAssertions.assertAllServices; + +/** + * @author wind57 + */ +class Fabric8DiscoveryAllServicesIT extends Fabric8DiscoveryBase { + + private static Service externalServiceName; + + @BeforeAll + static void beforeAllInNested() { + InputStream externalNameServiceStream = util.inputStream("external-name-service.yaml"); + externalServiceName = Serialization.unmarshal(externalNameServiceStream, Service.class); + } + + private void externalNameServices(Phase phase) { + if (phase == Phase.CREATE) { + util.createAndWait(NAMESPACE, null, null, externalServiceName, null, true); + } + else { + util.deleteAndWait(NAMESPACE, null, externalServiceName, null); + } + } + + @Nested + @TestPropertySource(properties = { "spring.cloud.kubernetes.discovery.include-external-name-services=true" }) + class NonBootstrap { + + @Autowired + private DiscoveryClient discoveryClient; + + @BeforeEach + void beforeEach() { + Images.loadBusybox(K3S); + util.busybox(NAMESPACE, Phase.CREATE); + externalNameServices(Phase.CREATE); + } + + @AfterEach + void afterEach() { + util.busybox(NAMESPACE, Phase.DELETE); + externalNameServices(Phase.DELETE); + } + + /** + *
+		 * 		- there are 3 services : 'busybox-service', 'kubernetes', 'external-name-service'
+		 * 		- all of them are found
+		 * 
+ */ + @Test + void test() { + assertAllServices(discoveryClient); + } + + } + + @Nested + @TestPropertySource(properties = { "spring.cloud.kubernetes.discovery.include-external-name-services=true", + "spring.cloud.bootstrap.enabled=true" }) + class Bootstrap { + + @Autowired + private DiscoveryClient discoveryClient; + + @BeforeEach + void beforeEach() { + Images.loadBusybox(K3S); + util.busybox(NAMESPACE, Phase.CREATE); + externalNameServices(Phase.CREATE); + } + + @AfterEach + void afterEach() { + util.busybox(NAMESPACE, Phase.DELETE); + externalNameServices(Phase.DELETE); + } + + /** + *
+		 * 		- there are 3 services : 'busybox-service', 'kubernetes', 'external-name-service'
+		 * 		- all of them are found
+		 * 
+ */ + @Test + void test() { + assertAllServices(discoveryClient); + } + + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBase.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBase.java new file mode 100644 index 00000000..0b385736 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBase.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2024 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.client.discovery; + +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.k3s.K3sContainer; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.test.context.TestPropertySource; + +/** + * @author wind57 + */ +@TestPropertySource(properties = { "spring.main.cloud-platform=kubernetes", + "spring.cloud.config.import-check.enabled=false", "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.discovery.metadata.add-pod-labels=true", + "spring.cloud.kubernetes.discovery.metadata.add-pod-annotations=true", + "logging.level.org.springframework.cloud.kubernetes.fabric8.discovery=debug" }) +@ExtendWith(OutputCaptureExtension.class) +@SpringBootTest(classes = { Fabric8DiscoveryApp.class, Fabric8DiscoveryBase.TestConfig.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +abstract class Fabric8DiscoveryBase { + + protected static final String NAMESPACE = "default"; + + protected static final K3sContainer K3S = Commons.container(); + + protected static Util util; + + @BeforeAll + protected static void beforeAll() { + K3S.start(); + util = new Util(K3S); + } + + protected static KubernetesClient client() { + String kubeConfigYaml = K3S.getKubeConfigYaml(); + Config config = Config.fromKubeconfig(kubeConfigYaml); + return new KubernetesClientBuilder().withConfig(config).build(); + } + + @TestConfiguration + static class TestConfig { + + @Bean + @Primary + KubernetesClient kubernetesClient() { + return client(); + } + + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBlockingIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBlockingIT.java new file mode 100644 index 00000000..3c4d3cc7 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBlockingIT.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2024 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.client.discovery; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.web.server.LocalManagementPort; +import org.springframework.cloud.kubernetes.integration.tests.commons.Images; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.test.context.TestPropertySource; + +import static org.springframework.cloud.kubernetes.fabric8.client.discovery.TestAssertions.assertBlockingConfiguration; + +/** + * @author wind57 + */ +@TestPropertySource(properties = { "spring.cloud.discovery.reactive.enabled=false", + "logging.level.org.springframework.cloud.client.discovery.health=DEBUG", + "logging.level.org.springframework.cloud.kubernetes.commons.discovery=DEBUG" }) +class Fabric8DiscoveryBlockingIT extends Fabric8DiscoveryBase { + + @LocalManagementPort + private int port; + + @BeforeEach + void beforeEach() { + Images.loadBusybox(K3S); + util.busybox(NAMESPACE, Phase.CREATE); + } + + @AfterEach + void afterEach() { + util.busybox(NAMESPACE, Phase.DELETE); + } + + @Test + void test(CapturedOutput output) { + assertBlockingConfiguration(output, port); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBoostrapDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBoostrapDelegate.java deleted file mode 100644 index 6917d0d8..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryBoostrapDelegate.java +++ /dev/null @@ -1,58 +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.client.discovery; - -import java.util.List; - -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.client.discovery.Fabric8DiscoveryClientUtil.builder; -import static org.springframework.cloud.kubernetes.fabric8.client.discovery.Fabric8DiscoveryClientUtil.retrySpec; - -/** - * @author wind57 - */ -final class Fabric8DiscoveryBoostrapDelegate { - - /** - * KubernetesDiscoveryClient::getServices call must include the external-name-service - * also. - */ - static void testAllServicesWithBootstrap() { - 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")); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryClientHealthDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryClientHealthDelegate.java deleted file mode 100644 index b9bfaa18..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryClientHealthDelegate.java +++ /dev/null @@ -1,199 +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.client.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.client.discovery.Fabric8DiscoveryClientUtil.builder; -import static org.springframework.cloud.kubernetes.fabric8.client.discovery.Fabric8DiscoveryClientUtil.retrySpec; -import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.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/client/discovery/Fabric8DiscoveryClientUtil.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryClientUtil.java deleted file mode 100644 index 3fb07d1b..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryClientUtil.java +++ /dev/null @@ -1,326 +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.client.discovery; - -import java.time.Duration; -import java.util.Objects; - -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; - -/** - * @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" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "FALSE" - } - ] - }] - } - } - } - } - """; - - static final String BODY_ONE_WITH_BOOTSTRAP = """ - { - "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" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "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" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_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" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "FALSE" - } - ] - }] - } - } - } - } - """; - - 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" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_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" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "FALSE" - } - ] - }] - } - } - } - } - """; - - 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" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "FALSE" - } - ] - }] - } - } - } - } - """; - - 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" - }, - { - "name": "SPRING_CLOUD_BOOTSTRAP_ENABLED", - "value": "FALSE" - } - ] - }] - } - } - } - } - """; - - 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); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryDefaultConfigurationIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryDefaultConfigurationIT.java new file mode 100644 index 00000000..438ae6e8 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryDefaultConfigurationIT.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2024 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.client.discovery; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.web.server.LocalManagementPort; +import org.springframework.cloud.kubernetes.integration.tests.commons.Images; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.test.context.TestPropertySource; + +import static org.springframework.cloud.kubernetes.fabric8.client.discovery.TestAssertions.testDefaultConfiguration; + +/** + * @author wind57 + */ +@TestPropertySource(properties = { "logging.level.org.springframework.cloud.client.discovery.health.reactive=DEBUG", + "logging.level.org.springframework.cloud.client.discovery.health=DEBUG", + "logging.level.org.springframework.cloud.kubernetes.fabric8.discovery.reactive=DEBUG", + "logging.level.org.springframework.cloud.kubernetes.fabric8.discovery=DEBUG", + "logging.level.org.springframework.cloud.kubernetes.commons.discovery=DEBUG" }) +class Fabric8DiscoveryDefaultConfigurationIT extends Fabric8DiscoveryBase { + + @LocalManagementPort + private int port; + + @BeforeEach + void beforeEach() { + Images.loadBusybox(K3S); + util.busybox(NAMESPACE, Phase.CREATE); + } + + @AfterEach + void afterEach() { + util.busybox(NAMESPACE, Phase.DELETE); + } + + @Test + void test(CapturedOutput output) { + testDefaultConfiguration(output, port); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryDelegate.java deleted file mode 100644 index 5b414698..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryDelegate.java +++ /dev/null @@ -1,88 +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.client.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.client.discovery.Fabric8DiscoveryClientUtil.builder; -import static org.springframework.cloud.kubernetes.fabric8.client.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/client/discovery/Fabric8DiscoveryFilterDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterDelegate.java deleted file mode 100644 index bbdd789c..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterDelegate.java +++ /dev/null @@ -1,140 +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.client.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.client.discovery.Fabric8DiscoveryClientUtil.builder; -import static org.springframework.cloud.kubernetes.fabric8.client.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/client/discovery/Fabric8DiscoveryFilterMatchOneNamespaceIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchOneNamespaceIT.java new file mode 100644 index 00000000..f7716bd6 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchOneNamespaceIT.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2024 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.client.discovery; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.kubernetes.integration.tests.commons.Images; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.test.context.TestPropertySource; + +import static org.springframework.cloud.kubernetes.fabric8.client.discovery.TestAssertions.filterMatchesOneNamespaceViaThePredicate; + +/** + * @author wind57 + */ +@TestPropertySource(properties = { "spring.cloud.kubernetes.discovery.namespaces[0]=a-uat", + "spring.cloud.kubernetes.discovery.namespaces[1]=b-uat", + "spring.cloud.kubernetes.discovery.filter=#root.metadata.namespace matches 'a-uat$'", + "logging.level.org.springframework.cloud.kubernetes.fabric8.discovery=DEBUG" + +}) +class Fabric8DiscoveryFilterMatchOneNamespaceIT extends Fabric8DiscoveryBase { + + private static final String NAMESPACE_A_UAT = "a-uat"; + + private static final String NAMESPACE_B_UAT = "b-uat"; + + @Autowired + private DiscoveryClient discoveryClient; + + @BeforeEach + void beforeEach() { + Images.loadWiremock(K3S); + + util.createNamespace(NAMESPACE_A_UAT); + util.createNamespace(NAMESPACE_B_UAT); + + util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.CREATE, false); + util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.CREATE, false); + + } + + @AfterEach + void afterEach() { + + util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.DELETE, false); + util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.DELETE, false); + + util.deleteNamespace(NAMESPACE_A_UAT); + util.deleteNamespace(NAMESPACE_B_UAT); + } + + @Test + void test() { + filterMatchesOneNamespaceViaThePredicate(discoveryClient); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchTwoNamespacesIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchTwoNamespacesIT.java new file mode 100644 index 00000000..4872c209 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryFilterMatchTwoNamespacesIT.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2024 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.client.discovery; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.kubernetes.integration.tests.commons.Images; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.test.context.TestPropertySource; + +import static org.springframework.cloud.kubernetes.fabric8.client.discovery.TestAssertions.filterMatchesBothNamespacesViaThePredicate; + +/** + * @author wind57 + */ +@TestPropertySource(properties = { "spring.cloud.kubernetes.discovery.namespaces[0]=a-uat", + "spring.cloud.kubernetes.discovery.namespaces[1]=b-uat", + "spring.cloud.kubernetes.discovery.filter=#root.metadata.namespace matches '^.*uat$'", + "logging.level.org.springframework.cloud.kubernetes.fabric8.discovery=DEBUG" + +}) +class Fabric8DiscoveryFilterMatchTwoNamespacesIT extends Fabric8DiscoveryBase { + + private static final String NAMESPACE_A_UAT = "a-uat"; + + private static final String NAMESPACE_B_UAT = "b-uat"; + + @Autowired + private DiscoveryClient discoveryClient; + + @BeforeEach + void beforeEach() { + Images.loadWiremock(K3S); + + util.createNamespace(NAMESPACE_A_UAT); + util.createNamespace(NAMESPACE_B_UAT); + + util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.CREATE, false); + util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.CREATE, false); + + } + + @AfterEach + void afterEach() { + + util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.DELETE, false); + util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.DELETE, false); + + util.deleteNamespace(NAMESPACE_A_UAT); + util.deleteNamespace(NAMESPACE_B_UAT); + } + + @Test + void test() { + filterMatchesBothNamespacesViaThePredicate(discoveryClient); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryNamespaceDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryNamespaceDelegate.java deleted file mode 100644 index bf9c7998..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryNamespaceDelegate.java +++ /dev/null @@ -1,67 +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.client.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.client.discovery.Fabric8DiscoveryClientUtil.builder; -import static org.springframework.cloud.kubernetes.fabric8.client.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/client/discovery/Fabric8DiscoveryPodMetadataIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryPodMetadataIT.java index d9e3dfe2..46d0e6ce 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryPodMetadataIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryPodMetadataIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -16,288 +16,56 @@ package org.springframework.cloud.kubernetes.fabric8.client.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.api.model.rbac.ClusterRoleBinding; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.utils.Serialization; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.AfterEach; +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.beans.factory.annotation.Autowired; +import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.kubernetes.integration.tests.commons.Images; 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; -import static org.springframework.cloud.kubernetes.fabric8.client.discovery.Fabric8DiscoveryClientUtil.BODY_FIVE; -import static org.springframework.cloud.kubernetes.fabric8.client.discovery.Fabric8DiscoveryClientUtil.BODY_FOUR; -import static org.springframework.cloud.kubernetes.fabric8.client.discovery.Fabric8DiscoveryClientUtil.BODY_ONE; -import static org.springframework.cloud.kubernetes.fabric8.client.discovery.Fabric8DiscoveryClientUtil.BODY_ONE_WITH_BOOTSTRAP; -import static org.springframework.cloud.kubernetes.fabric8.client.discovery.Fabric8DiscoveryClientUtil.BODY_SEVEN; -import static org.springframework.cloud.kubernetes.fabric8.client.discovery.Fabric8DiscoveryClientUtil.BODY_SIX; -import static org.springframework.cloud.kubernetes.fabric8.client.discovery.Fabric8DiscoveryClientUtil.BODY_THREE; -import static org.springframework.cloud.kubernetes.fabric8.client.discovery.Fabric8DiscoveryClientUtil.BODY_TWO; -import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.pomVersion; +import static org.springframework.cloud.kubernetes.fabric8.client.discovery.TestAssertions.assertPodMetadata; /** * @author wind57 */ -class Fabric8DiscoveryPodMetadataIT { +class Fabric8DiscoveryPodMetadataIT extends Fabric8DiscoveryBase { - 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; - - 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); + @Autowired + private DiscoveryClient discoveryClient; + @BeforeEach + void beforeEach() { Images.loadBusybox(K3S); - Images.loadWiremock(K3S); - - util = new Util(K3S); - client = util.client(); - - util.setUp(NAMESPACE); - - manifests(Phase.CREATE); - util.wiremock(NAMESPACE, "/wiremock", Phase.CREATE, false); util.busybox(NAMESPACE, Phase.CREATE); - - util.createNamespace(NAMESPACE_A_UAT); - util.createNamespace(NAMESPACE_B_UAT); - util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.CREATE, false); - util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.CREATE, false); - - util.createNamespace(NAMESPACE_LEFT); - util.createNamespace(NAMESPACE_RIGHT); - util.wiremock(NAMESPACE_LEFT, "/wiremock", Phase.CREATE, false); - util.wiremock(NAMESPACE_RIGHT, "/wiremock", Phase.CREATE, false); } - @AfterAll - static void after() { - util.wiremock(NAMESPACE, "/wiremock", Phase.DELETE, false); + @AfterEach + void afterEach() { util.busybox(NAMESPACE, Phase.DELETE); - - util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.DELETE, false); - util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.DELETE, false); - util.deleteNamespace(NAMESPACE_A_UAT); - util.deleteNamespace(NAMESPACE_B_UAT); - - util.wiremock(NAMESPACE_LEFT, "/wiremock", Phase.DELETE, false); - util.wiremock(NAMESPACE_RIGHT, "/wiremock", Phase.DELETE, false); - util.deleteNamespace(NAMESPACE_LEFT); - util.deleteNamespace(NAMESPACE_RIGHT); - - manifests(Phase.DELETE); } + /** + *
+	 * 		- there is a 'busybox-service' service deployed with two pods
+	 * 		- find each of the pod, add annotation to one, and labels to another
+	 * 		- call DiscoveryClient::getInstances with this serviceId and assert fields returned
+	 * 
+ */ @Test - void testPodMetadata() throws Exception { - - // find both pods - String[] both = K3S.execInContainer("sh", "-c", "kubectl get pods -l app=busybox -o=name --no-headers") + void test() throws Exception { + String[] busyboxPods = K3S.execInContainer("sh", "-c", "kubectl get pods -l app=busybox -o=name --no-headers") .getStdout() .split("\n"); - // add a label to first pod - K3S.execInContainer("sh", "-c", - "kubectl label pods " + both[0].split("/")[1] + " custom-label=custom-label-value"); - // add annotation to the second pod - K3S.execInContainer("sh", "-c", - "kubectl annotate pods " + both[1].split("/")[1] + " custom-annotation=custom-annotation-value"); - WebClient client = builder().baseUrl("http://localhost/service-instances/busybox-service").build(); - List serviceInstances = client.method(HttpMethod.GET) - .retrieve() - .bodyToMono(new ParameterizedTypeReference>() { + String podOne = busyboxPods[0].split("/")[1]; + String podTwo = busyboxPods[1].split("/")[1]; - }) - .retryWhen(retrySpec()) - .block(); + K3S.execInContainer("sh", "-c", "kubectl label pods " + podOne + " my-label=my-value"); + K3S.execInContainer("sh", "-c", "kubectl annotate pods " + podTwo + " my-annotation=my-value"); - DefaultKubernetesServiceInstance withCustomLabel = serviceInstances.stream() - .filter(x -> x.podMetadata().getOrDefault("annotations", Map.of()).isEmpty()) - .toList() - .get(0); - Assertions.assertEquals(withCustomLabel.getServiceId(), "busybox-service"); - Assertions.assertNotNull(withCustomLabel.getInstanceId()); - Assertions.assertNotNull(withCustomLabel.getHost()); - Assertions.assertEquals(withCustomLabel.getMetadata(), - Map.of("k8s_namespace", "default", "type", "ClusterIP", "port.busybox-port", "80")); - Assertions.assertTrue(withCustomLabel.podMetadata() - .get("labels") - .entrySet() - .stream() - .anyMatch(x -> x.getKey().equals("custom-label") && x.getValue().equals("custom-label-value"))); - - DefaultKubernetesServiceInstance withCustomAnnotation = serviceInstances.stream() - .filter(x -> !x.podMetadata().getOrDefault("annotations", Map.of()).isEmpty()) - .toList() - .get(0); - Assertions.assertEquals(withCustomAnnotation.getServiceId(), "busybox-service"); - Assertions.assertNotNull(withCustomAnnotation.getInstanceId()); - Assertions.assertNotNull(withCustomAnnotation.getHost()); - Assertions.assertEquals(withCustomAnnotation.getMetadata(), - 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(); - testAllServicesWithBootstrap(); - 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 testAllServicesWithBootstrap() { - util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_ONE_WITH_BOOTSTRAP, - Map.of("app", IMAGE_NAME)); - Fabric8DiscoveryBoostrapDelegate.testAllServicesWithBootstrap(); - } - - 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 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 = Serialization.unmarshal(deploymentStream, Deployment.class); - - List existing = new ArrayList<>( - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv()); - existing.add(new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_METADATA_ADDPODLABELS") - .withValue("true") - .build()); - existing.add(new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_METADATA_ADDPODANNOTATIONS") - .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 externalServiceName = Serialization.unmarshal(externalNameServiceStream, Service.class); - Service discoveryService = Serialization.unmarshal(discoveryServiceStream, Service.class); - Ingress ingress = Serialization.unmarshal(ingressStream, Ingress.class); - - ClusterRoleBinding clusterRoleBinding = Serialization.unmarshal(getAdminRole(), ClusterRoleBinding.class); - if (phase.equals(Phase.CREATE)) { - client.rbac().clusterRoleBindings().resource(clusterRoleBinding).create(); - util.createAndWait(NAMESPACE, IMAGE_NAME, deployment, discoveryService, ingress, true); - util.createAndWait(NAMESPACE, null, null, externalServiceName, null, true); - } - else { - client.rbac().clusterRoleBindings().resource(clusterRoleBinding).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())); - } - - private RetryBackoffSpec retrySpec() { - return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); + assertPodMetadata(discoveryClient); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryReactiveIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryReactiveIT.java new file mode 100644 index 00000000..12cf477f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/Fabric8DiscoveryReactiveIT.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2024 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.client.discovery; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.web.server.LocalManagementPort; +import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.kubernetes.integration.tests.commons.Images; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.test.context.TestPropertySource; + +import static org.springframework.cloud.kubernetes.fabric8.client.discovery.TestAssertions.testReactiveConfiguration; + +/** + * @author wind57 + */ +@TestPropertySource(properties = { "logging.level.org.springframework.cloud.kubernetes.commons.discovery=DEBUG", + "logging.level.org.springframework.cloud.client.discovery.health.reactive=DEBUG", + "logging.level.org.springframework.cloud.kubernetes.fabric8.discovery.reactive=DEBUG", + "logging.level.org.springframework.cloud.kubernetes.fabric8.discovery=DEBUG", + "spring.cloud.discovery.blocking.enabled=false" }) +class Fabric8DiscoveryReactiveIT extends Fabric8DiscoveryBase { + + @LocalManagementPort + private int port; + + @Autowired + private ReactiveDiscoveryClient discoveryClient; + + @BeforeEach + void beforeEach() { + Images.loadBusybox(K3S); + util.busybox(NAMESPACE, Phase.CREATE); + } + + @AfterEach + void afterEach() { + util.busybox(NAMESPACE, Phase.DELETE); + } + + @Test + void test(CapturedOutput output) { + testReactiveConfiguration(discoveryClient, output, port); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/TestAssertions.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/TestAssertions.java new file mode 100644 index 00000000..64ca0ec8 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/client/discovery/TestAssertions.java @@ -0,0 +1,332 @@ +/* + * Copyright 2012-2024 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.client.discovery; + +import java.time.Duration; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +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.boot.test.system.CapturedOutput; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +import static java.util.AbstractMap.SimpleEntry; +import static java.util.Map.Entry; +import static org.assertj.core.api.Assertions.assertThat; +import static org.testcontainers.shaded.org.awaitility.Awaitility.await; + +/** + * @author wind57 + */ +final class TestAssertions { + + 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(TestAssertions.class); + + private TestAssertions() { + + } + + static void assertPodMetadata(DiscoveryClient discoveryClient) { + + List serviceInstances = discoveryClient.getInstances("busybox-service"); + + // if annotations are empty, we got the other pod, with labels here + DefaultKubernetesServiceInstance withCustomLabel = serviceInstances.stream() + .map(instance -> (DefaultKubernetesServiceInstance) instance) + .filter(x -> x.podMetadata().getOrDefault("annotations", Map.of()).isEmpty()) + .toList() + .get(0); + List> podMetadataLabels = withCustomLabel.podMetadata() + .get("labels") + .entrySet() + .stream() + .toList(); + + assertThat(withCustomLabel.getServiceId()).isEqualTo("busybox-service"); + assertThat(withCustomLabel.getInstanceId()).isNotNull(); + assertThat(withCustomLabel.getHost()).isNotNull(); + assertThat(withCustomLabel.getMetadata()) + .isEqualTo(Map.of("k8s_namespace", "default", "type", "ClusterIP", "port.busybox-port", "80")); + assertThat(podMetadataLabels).contains(new SimpleEntry<>("my-label", "my-value")); + + // if annotation are present, we got the one with annotations here + DefaultKubernetesServiceInstance withCustomAnnotation = serviceInstances.stream() + .map(instance -> (DefaultKubernetesServiceInstance) instance) + .filter(x -> !x.podMetadata().getOrDefault("annotations", Map.of()).isEmpty()) + .toList() + .get(0); + List> podMetadataAnnotations = withCustomAnnotation.podMetadata() + .get("annotations") + .entrySet() + .stream() + .toList(); + + assertThat(withCustomLabel.getServiceId()).isEqualTo("busybox-service"); + assertThat(withCustomLabel.getInstanceId()).isNotNull(); + assertThat(withCustomLabel.getHost()).isNotNull(); + assertThat(withCustomLabel.getMetadata()) + .isEqualTo(Map.of("k8s_namespace", "default", "type", "ClusterIP", "port.busybox-port", "80")); + assertThat(podMetadataAnnotations).contains(new SimpleEntry<>("my-annotation", "my-value")); + } + + static void assertAllServices(DiscoveryClient discoveryClient) { + + List services = discoveryClient.getServices(); + assertThat(services).containsExactlyInAnyOrder("kubernetes", "busybox-service", "external-name-service"); + + ServiceInstance externalNameInstance = discoveryClient.getInstances("external-name-service").get(0); + + assertThat(externalNameInstance.getServiceId()).isEqualTo("external-name-service"); + assertThat(externalNameInstance.getInstanceId()).isNotNull(); + assertThat(externalNameInstance.getHost()).isEqualTo("spring.io"); + assertThat(externalNameInstance.getPort()).isEqualTo(-1); + assertThat(externalNameInstance.getMetadata()) + .isEqualTo(Map.of("k8s_namespace", "default", "type", "ExternalName")); + assertThat(externalNameInstance.isSecure()).isFalse(); + assertThat(externalNameInstance.getUri().toASCIIString()).isEqualTo("spring.io"); + assertThat(externalNameInstance.getScheme()).isEqualTo("http"); + } + + /** + * 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 assertBlockingConfiguration(CapturedOutput output, int port) { + + waitForLogStatement(output, "Will publish InstanceRegisteredEvent from blocking implementation"); + waitForLogStatement(output, "publishing InstanceRegisteredEvent"); + waitForLogStatement(output, "Discovery Client has been initialized"); + + WebClient healthClient = builder().baseUrl("http://localhost:" + port + "/actuator/health").build(); + + String healthResult = healthClient.method(HttpMethod.GET) + .retrieve() + .bodyToMono(String.class) + .retryWhen(retrySpec()) + .block(); + + assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathStringValue("$.components.discoveryComposite.status") + .isEqualTo("UP"); + + assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(BLOCKING_STATUS).isEqualTo("UP"); + + assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathArrayValue("$.components.discoveryComposite.components.discoveryClient.details.services") + .containsExactlyInAnyOrder("kubernetes", "busybox-service"); + + assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(REACTIVE_STATUS); + + } + + /** + * Both blocking and reactive are enabled. + */ + static void testDefaultConfiguration(CapturedOutput output, int port) { + + waitForLogStatement(output, "Will publish InstanceRegisteredEvent from blocking implementation"); + waitForLogStatement(output, "Will publish InstanceRegisteredEvent from reactive implementation"); + waitForLogStatement(output, "publishing InstanceRegisteredEvent"); + waitForLogStatement(output, "Discovery Client has been initialized"); + + WebClient healthClient = builder().baseUrl("http://localhost:" + port + "/actuator/health").build(); + + String healthResult = healthClient.method(HttpMethod.GET) + .retrieve() + .bodyToMono(String.class) + .retryWhen(retrySpec()) + .block(); + + assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathStringValue("$.components.discoveryComposite.status") + .isEqualTo("UP"); + + assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathStringValue("$.components.discoveryComposite.components.discoveryClient.status") + .isEqualTo("UP"); + + assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathArrayValue("$.components.discoveryComposite.components.discoveryClient.details.services") + .containsExactlyInAnyOrder("kubernetes", "busybox-service"); + + assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status") + .isEqualTo("UP"); + + assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue( + "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].status") + .isEqualTo("UP"); + + assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue( + "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].details.services") + .containsExactlyInAnyOrder("kubernetes", "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 reactive discovery + * client was initialized. + */ + static void testReactiveConfiguration(ReactiveDiscoveryClient discoveryClient, CapturedOutput output, int port) { + + waitForLogStatement(output, "Will publish InstanceRegisteredEvent from reactive implementation"); + waitForLogStatement(output, "publishing InstanceRegisteredEvent"); + waitForLogStatement(output, "Discovery Client has been initialized"); + + WebClient healthClient = builder().baseUrl("http://localhost:" + port + "/actuator/health").build(); + + String healthResult = healthClient.method(HttpMethod.GET) + .retrieve() + .bodyToMono(String.class) + .retryWhen(retrySpec()) + .block(); + + assertThat(BASIC_JSON_TESTER.from(healthResult)) + .extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status") + .isEqualTo("UP"); + + assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(REACTIVE_STATUS).isEqualTo("UP"); + + assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue( + "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].details.services") + .containsExactlyInAnyOrder("kubernetes", "busybox-service"); + + assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(BLOCKING_STATUS); + + List services = discoveryClient.getServices().toStream().toList(); + + assertThat(services).contains("busybox-service"); + assertThat(services).contains("kubernetes"); + + } + + /** + *
+	 *     - 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(DiscoveryClient discoveryClient) { + + List services = discoveryClient.getServices(); + + assertThat(Objects.requireNonNull(services).size()).isEqualTo(1); + assertThat(services).contains("service-wiremock"); + + List serviceInstances = discoveryClient.getInstances("service-wiremock") + .stream() + .map(x -> (DefaultKubernetesServiceInstance) x) + .toList(); + + assertThat(Objects.requireNonNull(serviceInstances).size()).isEqualTo(2); + List sorted = serviceInstances.stream() + .sorted(Comparator.comparing(DefaultKubernetesServiceInstance::getNamespace)) + .toList(); + + DefaultKubernetesServiceInstance first = sorted.get(0); + assertThat(first.getServiceId()).isEqualTo("service-wiremock"); + assertThat(first.getInstanceId()).isNotNull(); + assertThat(first.getPort()).isEqualTo(8080); + assertThat(first.getNamespace()).isEqualTo("a-uat"); + assertThat(first.getMetadata()).isEqualTo( + Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP")); + + DefaultKubernetesServiceInstance second = sorted.get(1); + assertThat(second.getServiceId()).isEqualTo("service-wiremock"); + assertThat(second.getInstanceId()).isNotNull(); + assertThat(second.getPort()).isEqualTo(8080); + assertThat(second.getNamespace()).isEqualTo("b-uat"); + assertThat(second.getMetadata()).isEqualTo( + 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(DiscoveryClient discoveryClient) { + + List services = discoveryClient.getServices(); + assertThat(services.size()).isEqualTo(1); + assertThat(services).contains("service-wiremock"); + + List serviceInstances = discoveryClient.getInstances("service-wiremock") + .stream() + .map(x -> (DefaultKubernetesServiceInstance) x) + .toList(); + + assertThat(serviceInstances.size()).isEqualTo(1); + + DefaultKubernetesServiceInstance first = serviceInstances.get(0); + assertThat(first.getServiceId()).isEqualTo("service-wiremock"); + assertThat(first.getInstanceId()).isNotNull(); + assertThat(first.getPort()).isEqualTo(8080); + assertThat(first.getNamespace()).isEqualTo("a-uat"); + assertThat(first.getMetadata()).isEqualTo( + Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP")); + + } + + private static void waitForLogStatement(CapturedOutput output, String message) { + await().pollInterval(Duration.ofSeconds(1)) + .atMost(Duration.ofSeconds(30)) + .until(() -> output.getOut().contains(message)); + } + + private static WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private static 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/resources/fabric8-discovery-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-deployment.yaml deleted file mode 100644 index 47f0a16f..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-deployment.yaml +++ /dev/null @@ -1,31 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: spring-cloud-kubernetes-fabric8-client-discovery-deployment -spec: - selector: - matchLabels: - app: spring-cloud-kubernetes-fabric8-client-discovery - template: - metadata: - labels: - app: spring-cloud-kubernetes-fabric8-client-discovery - spec: - serviceAccountName: spring-cloud-kubernetes-serviceaccount - containers: - - name: spring-cloud-kubernetes-fabric8-client-discovery - image: docker.io/springcloud/spring-cloud-kubernetes-fabric8-client-discovery - imagePullPolicy: IfNotPresent - readinessProbe: - httpGet: - port: 8080 - path: /actuator/health/readiness - livenessProbe: - httpGet: - port: 8080 - path: /actuator/health/liveness - ports: - - containerPort: 8080 - env: - - name: SPRING_CLOUD_BOOTSTRAP_ENABLED - value: "FALSE" diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-ingress.yaml deleted file mode 100644 index 334508bf..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-ingress.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: spring-cloud-kubernetes-fabric8-client-discovery-ingress - namespace: default -spec: - rules: - - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: spring-cloud-kubernetes-fabric8-client-discovery - port: - number: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-service.yaml deleted file mode 100644 index bea511d5..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: spring-cloud-kubernetes-fabric8-client-discovery - name: spring-cloud-kubernetes-fabric8-client-discovery -spec: - ports: - - name: http - port: 8080 - targetPort: 8080 - selector: - app: spring-cloud-kubernetes-fabric8-client-discovery - type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml deleted file mode 100644 index d8eec949..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - creationTimestamp: null - name: admin-default -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cluster-admin -subjects: - - kind: ServiceAccount - name: spring-cloud-kubernetes-serviceaccount - namespace: default