diff --git a/spring-cloud-kubernetes-integration-tests/pom.xml b/spring-cloud-kubernetes-integration-tests/pom.xml index 261c2e1e5a..c17152c451 100644 --- a/spring-cloud-kubernetes-integration-tests/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/pom.xml @@ -77,6 +77,7 @@ + ${project.build.outputDirectory} ${testsToRun} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/pom.xml index 0c1e1e3eb7..33be033dd6 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/pom.xml @@ -42,6 +42,22 @@ true + + + + org.springframework.boot + spring-boot-maven-plugin + + + build-image + + true + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchBase.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchBase.java new file mode 100644 index 0000000000..5dd33c44ba --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchBase.java @@ -0,0 +1,75 @@ +/* + * 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.catalog.watch; + +import java.util.Map; +import java.util.Set; + +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.system.OutputCaptureExtension; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util; +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.discovery.catalogServicesWatchDelay=2000", + "spring.cloud.kubernetes.client.namespace=default", + "logging.level.org.springframework.cloud.kubernetes.fabric8.discovery=DEBUG" }) +@ExtendWith(OutputCaptureExtension.class) +abstract class Fabric8CatalogWatchBase { + + protected static final String NAMESPACE = "default"; + + protected static final String NAMESPACE_A = "a"; + + protected static final String NAMESPACE_B = "b"; + + protected static final K3sContainer K3S = Commons.container(); + + protected static Util util; + + @BeforeAll + protected static void beforeAll() { + K3S.start(); + util = new Util(K3S); + } + + protected static KubernetesDiscoveryProperties discoveryProperties(boolean useEndpointSlices) { + return new KubernetesDiscoveryProperties(true, false, Set.of(NAMESPACE, NAMESPACE_A), true, 60, false, null, + Set.of(443, 8443), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, useEndpointSlices, + false, null); + } + + protected static KubernetesClient client() { + String kubeConfigYaml = K3S.getKubeConfigYaml(); + Config config = Config.fromKubeconfig(kubeConfigYaml); + return new KubernetesClientBuilder().withConfig(config).build(); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchIT.java deleted file mode 100644 index 652699eaad..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchIT.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright 2013-2022 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.catalog.watch; - -import java.io.InputStream; -import java.time.Duration; -import java.util.List; -import java.util.Set; - -import io.fabric8.kubernetes.api.model.Service; -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.api.model.networking.v1.Ingress; -import io.fabric8.kubernetes.client.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.Test; -import org.testcontainers.k3s.K3sContainer; - -import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace; -import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; -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.core.ResolvableType; -import org.springframework.http.HttpMethod; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.awaitility.Awaitility.await; -import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchUtil.builder; -import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchUtil.patchForEndpointSlices; -import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchUtil.patchForNamespaceFilterAndEndpointSlices; -import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchUtil.patchForNamespaceFilterAndEndpoints; -import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchUtil.retrySpec; -import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.pomVersion; - -/** - * @author wind57 - */ -class Fabric8CatalogWatchIT { - - private static final String NAMESPACE = "default"; - - public static final String NAMESPACE_A = "namespacea"; - - public static final String NAMESPACE_B = "namespaceb"; - - private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-catalog-watcher"; - - private static final String DOCKER_IMAGE = "docker.io/springcloud/" + IMAGE_NAME + ":" + pomVersion(); - - private static final K3sContainer K3S = Commons.container(); - - private static Util util; - - @BeforeAll - static void beforeAll() throws Exception { - K3S.start(); - Commons.validateImage(IMAGE_NAME, K3S); - Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); - - Images.loadBusybox(K3S); - - util = new Util(K3S); - - util.createNamespace(NAMESPACE_A); - util.createNamespace(NAMESPACE_B); - - util.setUp(NAMESPACE); - util.setUpClusterWide(NAMESPACE, Set.of(NAMESPACE, NAMESPACE_A, NAMESPACE_B)); - util.busybox(NAMESPACE, Phase.CREATE); - - app(Phase.CREATE); - } - - @AfterAll - static void afterAll() { - - util.deleteNamespace(NAMESPACE_A); - util.deleteNamespace(NAMESPACE_B); - - app(Phase.DELETE); - Commons.systemPrune(); - } - - /** - *
-	 *     - we deploy a busybox service with 2 replica pods
-	 *     - we receive an event from KubernetesCatalogWatcher, assert what is inside it
-	 *     - delete the busybox service
-	 *     - assert that we receive only spring-cloud-kubernetes-fabric8-client-catalog-watcher pod
-	 * 
- */ - @Test - void testCatalogWatchWithEndpoints() throws Exception { - assertLogStatement(); - test(); - - testCatalogWatchWithEndpointSlices(); - testCatalogWatchWithNamespaceFilterAndEndpoints(); - testCatalogWatchWithNamespaceFilterAndEndpointSlices(); - } - - void testCatalogWatchWithEndpointSlices() { - util.busybox(NAMESPACE, Phase.CREATE); - patchForEndpointSlices(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE); - Commons.waitForLogStatement("stateGenerator is of type: Fabric8EndpointSliceV1CatalogWatch", K3S, IMAGE_NAME); - test(); - } - - void testCatalogWatchWithNamespaceFilterAndEndpoints() { - util.busybox(NAMESPACE_A, Phase.CREATE); - util.busybox(NAMESPACE_B, Phase.CREATE); - patchForNamespaceFilterAndEndpoints(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE); - Fabric8CatalogWatchWithNamespacesDelegate.testCatalogWatchWithNamespaceFilterAndEndpoints(K3S, IMAGE_NAME, - util); - } - - void testCatalogWatchWithNamespaceFilterAndEndpointSlices() { - util.busybox(NAMESPACE_A, Phase.CREATE); - util.busybox(NAMESPACE_B, Phase.CREATE); - patchForNamespaceFilterAndEndpointSlices(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE); - Fabric8CatalogWatchWithNamespacesDelegate.testCatalogWatchWithNamespaceFilterAndEndpointSlices(K3S, IMAGE_NAME, - util); - } - - /** - * we log in debug mode the type of the StateGenerator we use, be that Endpoints or - * EndpointSlices. Here we make sure that in the test we actually use the correct - * type. - */ - private void assertLogStatement() throws Exception { - String appPodName = K3S - .execInContainer("kubectl", "get", "pods", "-l", - "app=spring-cloud-kubernetes-fabric8-client-catalog-watcher", "-o=name", "--no-headers") - .getStdout(); - String allLogs = K3S.execInContainer("kubectl", "logs", appPodName.trim()).getStdout(); - Assertions.assertTrue(allLogs.contains("stateGenerator is of type: Fabric8EndpointsCatalogWatch")); - } - - /** - * the test is the same for both endpoints and endpoint slices, the set-up for them is - * different. - */ - @SuppressWarnings("unchecked") - private void test() { - - WebClient client = builder().baseUrl("http://localhost/result").build(); - EndpointNameAndNamespace[] holder = new EndpointNameAndNamespace[2]; - ResolvableType resolvableType = ResolvableType.forClassWithGenerics(List.class, EndpointNameAndNamespace.class); - - await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(240)).until(() -> { - List result = (List) client.method(HttpMethod.GET) - .retrieve() - .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())) - .retryWhen(retrySpec()) - .block(); - - // we get 3 pods as input, but because they are sorted by name in the catalog - // watcher implementation - // we will get the first busybox instances here. - if (result != null) { - if (result.size() != 3) { - return false; - } - holder[0] = result.get(0); - holder[1] = result.get(1); - return true; - } - - return false; - }); - - EndpointNameAndNamespace resultOne = holder[0]; - EndpointNameAndNamespace resultTwo = holder[1]; - - Assertions.assertNotNull(resultOne); - Assertions.assertNotNull(resultTwo); - - Assertions.assertTrue(resultOne.endpointName().contains("busybox")); - Assertions.assertTrue(resultTwo.endpointName().contains("busybox")); - Assertions.assertEquals("default", resultOne.namespace()); - Assertions.assertEquals("default", resultTwo.namespace()); - - util.busybox(NAMESPACE, Phase.DELETE); - - // what we get after delete - EndpointNameAndNamespace[] afterDelete = new EndpointNameAndNamespace[1]; - - await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(240)).until(() -> { - List result = (List) client.method(HttpMethod.GET) - .retrieve() - .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())) - .retryWhen(retrySpec()) - .block(); - - // we need to get the event from KubernetesCatalogWatch, but that happens - // on periodic bases. So in order to be sure we got the event we care about - // we wait until the result has a single entry, which means busybox was - // deleted - // + KubernetesCatalogWatch received the new update. - if (result != null && result.size() != 1) { - return false; - } - - // we will only receive one pod here, our own - if (result != null) { - afterDelete[0] = result.get(0); - return true; - } - - return false; - }); - - Assertions.assertTrue(afterDelete[0].endpointName().contains(IMAGE_NAME)); - Assertions.assertEquals("default", afterDelete[0].namespace()); - - } - - private static void app(Phase phase) { - - InputStream endpointsDeploymentStream = util.inputStream("app/watcher-endpoints-deployment.yaml"); - InputStream serviceStream = util.inputStream("app/watcher-service.yaml"); - InputStream ingressStream = util.inputStream("app/watcher-ingress.yaml"); - - Deployment deployment = Serialization.unmarshal(endpointsDeploymentStream, Deployment.class); - Service service = Serialization.unmarshal(serviceStream, Service.class); - Ingress ingress = Serialization.unmarshal(ingressStream, Ingress.class); - - if (phase.equals(Phase.CREATE)) { - util.createAndWait(Fabric8CatalogWatchIT.NAMESPACE, null, deployment, service, ingress, true); - } - else { - util.deleteAndWait(Fabric8CatalogWatchIT.NAMESPACE, deployment, service, ingress); - } - - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchUtil.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchUtil.java deleted file mode 100644 index 16b9f818e2..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchUtil.java +++ /dev/null @@ -1,156 +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.catalog.watch; - -import java.time.Duration; -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.cloud.kubernetes.integration.tests.commons.fabric8_client.Util; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * @author wind57 - */ -final class Fabric8CatalogWatchUtil { - - private static final Map POD_LABELS = Map.of("app", - "spring-cloud-kubernetes-fabric8-client-catalog-watcher"); - - private Fabric8CatalogWatchUtil() { - - } - - static final String BODY_ONE = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-cloud-kubernetes-fabric8-client-catalog-watcher", - "image": "image_name_here", - "env": [ - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", - "value": "DEBUG" - }, - { - "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_USE_ENDPOINT_SLICES", - "value": "TRUE" - } - ] - }] - } - } - } - } - """; - - static final String BODY_TWO = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-cloud-kubernetes-fabric8-client-catalog-watcher", - "image": "image_name_here", - "env": [ - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", - "value": "DEBUG" - }, - { - "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_USE_ENDPOINT_SLICES", - "value": "FALSE" - }, - { - "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0", - "value": "namespacea" - }, - { - "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_1", - "value": "default" - } - ] - }] - } - } - } - } - """; - - static final String BODY_THREE = """ - { - "spec": { - "template": { - "spec": { - "containers": [{ - "name": "spring-cloud-kubernetes-fabric8-client-catalog-watcher", - "image": "image_name_here", - "env": [ - { - "name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY", - "value": "DEBUG" - }, - { - "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_USE_ENDPOINT_SLICES", - "value": "TRUE" - }, - { - "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0", - "value": "namespacea" - }, - { - "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_1", - "value": "default" - } - ] - }] - } - } - } - } - """; - - static void patchForEndpointSlices(Util util, String dockerImage, String deploymentName, String namespace) { - util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_ONE, POD_LABELS); - } - - static void patchForNamespaceFilterAndEndpoints(Util util, String dockerImage, String deploymentName, - String namespace) { - util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_TWO, POD_LABELS); - } - - static void patchForNamespaceFilterAndEndpointSlices(Util util, String dockerImage, String deploymentName, - String namespace) { - util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_THREE, POD_LABELS); - } - - 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-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithEndpointSlicesAndNamespaceFilterIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithEndpointSlicesAndNamespaceFilterIT.java new file mode 100644 index 0000000000..914cc4ab62 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithEndpointSlicesAndNamespaceFilterIT.java @@ -0,0 +1,101 @@ +/* + * 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.catalog.watch; + +import java.util.Set; + +import io.fabric8.kubernetes.client.KubernetesClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesCatalogWatchAutoConfiguration; +import org.springframework.cloud.kubernetes.integration.tests.commons.Images; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchWithEndpointSlicesAndNamespaceFilterIT.TestConfig; + +/** + * @author wind57 + */ +@SpringBootTest(classes = { KubernetesCatalogWatchAutoConfiguration.class, TestConfig.class, Application.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class Fabric8CatalogWatchWithEndpointSlicesAndNamespaceFilterIT extends Fabric8CatalogWatchBase { + + @LocalServerPort + private int port; + + @BeforeEach + void beforeEach() { + + util.createNamespace(NAMESPACE_A); + util.createNamespace(NAMESPACE_B); + + Images.loadBusybox(K3S); + + util.busybox(NAMESPACE_A, Phase.CREATE); + util.busybox(NAMESPACE_B, Phase.CREATE); + + } + + @AfterEach + void afterEach() { + util.deleteNamespace(NAMESPACE_A); + util.deleteNamespace(NAMESPACE_B); + } + + /** + *
+	 *     - we deploy a busybox service with 2 replica pods in two namespaces : a, b
+	 *     - we use endpoint slices
+	 *     - we enable namespace filtering for 'default' and 'a'
+	 *     - we receive an event from KubernetesCatalogWatcher, assert what is inside it
+	 *     - delete the busybox service in 'a' and 'b'
+	 *     - assert that we receive an empty response
+	 * 
+ */ + @Test + void test(CapturedOutput output) { + TestAssertions.assertLogStatement(output, "stateGenerator is of type: Fabric8EndpointSliceV1CatalogWatch"); + TestAssertions.invokeAndAssert(util, Set.of(NAMESPACE_A, NAMESPACE_B), port, NAMESPACE_A); + } + + @TestConfiguration + static class TestConfig { + + @Bean + @Primary + KubernetesClient kubernetesClient() { + return client(); + } + + @Bean + @Primary + KubernetesDiscoveryProperties kubernetesDiscoveryProperties() { + return discoveryProperties(true); + } + + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithEndpointSlicesIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithEndpointSlicesIT.java new file mode 100644 index 0000000000..e18b810c71 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithEndpointSlicesIT.java @@ -0,0 +1,86 @@ +/* + * 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.catalog.watch; + +import java.util.Set; + +import io.fabric8.kubernetes.client.KubernetesClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesCatalogWatchAutoConfiguration; +import org.springframework.cloud.kubernetes.integration.tests.commons.Images; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchWithEndpointSlicesIT.TestConfig; + +/** + * @author wind57 + */ +@SpringBootTest(classes = { KubernetesCatalogWatchAutoConfiguration.class, TestConfig.class, Application.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class Fabric8CatalogWatchWithEndpointSlicesIT extends Fabric8CatalogWatchBase { + + @LocalServerPort + private int port; + + @BeforeEach + void beforeEach() { + Images.loadBusybox(K3S); + util.busybox(NAMESPACE, Phase.CREATE); + } + + /** + *
+	 *     - we deploy a busybox service with 2 replica pods
+	 *     - we use endpoint slices
+	 *     - we receive an event from KubernetesCatalogWatcher, assert what is inside it
+	 *     - delete the busybox service
+	 *     - assert that we receive an empty response
+	 * 
+ */ + @Test + void test(CapturedOutput output) { + TestAssertions.assertLogStatement(output, "stateGenerator is of type: Fabric8EndpointSliceV1CatalogWatch"); + TestAssertions.invokeAndAssert(util, Set.of(NAMESPACE), port, NAMESPACE); + } + + @TestConfiguration + static class TestConfig { + + @Bean + @Primary + KubernetesClient kubernetesClient() { + return client(); + } + + @Bean + @Primary + KubernetesDiscoveryProperties kubernetesDiscoveryProperties() { + return discoveryProperties(true); + } + + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithEndpointsAndNamespaceFilterIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithEndpointsAndNamespaceFilterIT.java new file mode 100644 index 0000000000..9eccc88895 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithEndpointsAndNamespaceFilterIT.java @@ -0,0 +1,101 @@ +/* + * 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.catalog.watch; + +import java.util.Set; + +import io.fabric8.kubernetes.client.KubernetesClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesCatalogWatchAutoConfiguration; +import org.springframework.cloud.kubernetes.integration.tests.commons.Images; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchWithEndpointsAndNamespaceFilterIT.TestConfig; + +/** + * @author wind57 + */ +@SpringBootTest(classes = { KubernetesCatalogWatchAutoConfiguration.class, TestConfig.class, Application.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class Fabric8CatalogWatchWithEndpointsAndNamespaceFilterIT extends Fabric8CatalogWatchBase { + + @LocalServerPort + private int port; + + @BeforeEach + void beforeEach() { + + util.createNamespace(NAMESPACE_A); + util.createNamespace(NAMESPACE_B); + + Images.loadBusybox(K3S); + + util.busybox(NAMESPACE_A, Phase.CREATE); + util.busybox(NAMESPACE_B, Phase.CREATE); + + } + + @AfterEach + void afterEach() { + util.deleteNamespace(NAMESPACE_A); + util.deleteNamespace(NAMESPACE_B); + } + + /** + *
+	 *     - we deploy a busybox service with 2 replica pods in two namespaces : a, b
+	 *     - we use endpoints
+	 *     - we enable namespace filtering for 'default' and 'a'
+	 *     - we receive an event from KubernetesCatalogWatcher, assert what is inside it
+	 *     - delete the busybox service in 'a' and 'b'
+	 *     - assert that we receive an empty response
+	 * 
+ */ + @Test + void test(CapturedOutput output) { + TestAssertions.assertLogStatement(output, "stateGenerator is of type: Fabric8EndpointsCatalogWatch"); + TestAssertions.invokeAndAssert(util, Set.of(NAMESPACE_A, NAMESPACE_B), port, NAMESPACE_A); + } + + @TestConfiguration + static class TestConfig { + + @Bean + @Primary + KubernetesClient kubernetesClient() { + return client(); + } + + @Bean + @Primary + KubernetesDiscoveryProperties kubernetesDiscoveryProperties() { + return discoveryProperties(false); + } + + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithEndpointsIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithEndpointsIT.java new file mode 100644 index 0000000000..c8ca4e22ff --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithEndpointsIT.java @@ -0,0 +1,86 @@ +/* + * 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.catalog.watch; + +import java.util.Set; + +import io.fabric8.kubernetes.client.KubernetesClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesCatalogWatchAutoConfiguration; +import org.springframework.cloud.kubernetes.integration.tests.commons.Images; +import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchWithEndpointsIT.TestConfig; + +/** + * @author wind57 + */ +@SpringBootTest(classes = { KubernetesCatalogWatchAutoConfiguration.class, TestConfig.class, Application.class }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class Fabric8CatalogWatchWithEndpointsIT extends Fabric8CatalogWatchBase { + + @LocalServerPort + private int port; + + @BeforeEach + void beforeEach() { + Images.loadBusybox(K3S); + util.busybox(NAMESPACE, Phase.CREATE); + } + + /** + *
+	 *     - we deploy a busybox service with 2 replica pods
+	 *     - we use endpoints
+	 *     - we receive an event from KubernetesCatalogWatcher, assert what is inside it
+	 *     - delete the busybox service
+	 *     - assert that we receive an empty response
+	 * 
+ */ + @Test + void test(CapturedOutput output) { + TestAssertions.assertLogStatement(output, "stateGenerator is of type: Fabric8EndpointsCatalogWatch"); + TestAssertions.invokeAndAssert(util, Set.of(NAMESPACE), port, NAMESPACE); + } + + @TestConfiguration + static class TestConfig { + + @Bean + @Primary + KubernetesClient kubernetesClient() { + return client(); + } + + @Bean + @Primary + KubernetesDiscoveryProperties kubernetesDiscoveryProperties() { + return discoveryProperties(false); + } + + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithNamespacesDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithNamespacesDelegate.java deleted file mode 100644 index 69d4a06352..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Fabric8CatalogWatchWithNamespacesDelegate.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2013-2022 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.catalog.watch; - -import java.time.Duration; -import java.util.List; - -import org.junit.jupiter.api.Assertions; -import org.testcontainers.k3s.K3sContainer; - -import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace; -import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; -import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; -import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.ResolvableType; -import org.springframework.http.HttpMethod; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.awaitility.Awaitility.await; -import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchIT.NAMESPACE_A; -import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchIT.NAMESPACE_B; -import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchUtil.builder; -import static org.springframework.cloud.kubernetes.fabric8.catalog.watch.Fabric8CatalogWatchUtil.retrySpec; - -/** - * @author wind57 - */ -final class Fabric8CatalogWatchWithNamespacesDelegate { - - private Fabric8CatalogWatchWithNamespacesDelegate() { - - } - - private static final String APP_NAME = "spring-cloud-kubernetes-fabric8-client-catalog-watcher"; - - /** - *
-	 *     - we deploy one busybox service with 2 replica pods in namespace namespacea
-	 *     - we deploy one busybox service with 2 replica pods in namespace namespaceb
-	 *     - we enable the search to be made in namespacea and default ones
-	 *     - we receive an event from KubernetesCatalogWatcher, assert what is inside it
-	 *     - delete both busybox services in namespacea and namespaceb
-	 *     - assert that we receive only spring-cloud-kubernetes-fabric8-client-catalog-watcher pod
-	 * 
- */ - static void testCatalogWatchWithNamespaceFilterAndEndpoints(K3sContainer container, String imageName, Util util) { - Commons.waitForLogStatement("stateGenerator is of type: Fabric8EndpointsCatalogWatch", container, imageName); - test(util); - } - - static void testCatalogWatchWithNamespaceFilterAndEndpointSlices(K3sContainer container, String imageName, - Util util) { - Commons.waitForLogStatement("stateGenerator is of type: Fabric8EndpointSliceV1CatalogWatch", container, - imageName); - test(util); - } - - /** - * the test is the same for both endpoints and endpoint slices, the set-up for them is - * different. - */ - @SuppressWarnings("unchecked") - private static void test(Util util) { - - WebClient client = builder().baseUrl("http://localhost/result").build(); - EndpointNameAndNamespace[] holder = new EndpointNameAndNamespace[2]; - ResolvableType resolvableType = ResolvableType.forClassWithGenerics(List.class, EndpointNameAndNamespace.class); - - await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(240)).until(() -> { - List result = (List) client.method(HttpMethod.GET) - .retrieve() - .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())) - .retryWhen(retrySpec()) - .block(); - - // we get 3 pods as input, but because they are sorted by name in the catalog - // watcher implementation - // we will get the first busybox instances here. - if (result != null) { - holder[0] = result.get(0); - holder[1] = result.get(1); - return true; - } - - return false; - }); - - EndpointNameAndNamespace resultOne = holder[0]; - EndpointNameAndNamespace resultTwo = holder[1]; - - Assertions.assertNotNull(resultOne); - Assertions.assertNotNull(resultTwo); - - Assertions.assertTrue(resultOne.endpointName().contains("busybox")); - Assertions.assertTrue(resultTwo.endpointName().contains("busybox")); - Assertions.assertEquals(NAMESPACE_A, resultOne.namespace()); - Assertions.assertEquals(NAMESPACE_A, resultTwo.namespace()); - - util.busybox(NAMESPACE_A, Phase.DELETE); - util.busybox(NAMESPACE_B, Phase.DELETE); - - // what we get after delete - EndpointNameAndNamespace[] afterDelete = new EndpointNameAndNamespace[1]; - - await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(240)).until(() -> { - List result = (List) client.method(HttpMethod.GET) - .retrieve() - .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())) - .retryWhen(retrySpec()) - .block(); - - // we need to get the event from KubernetesCatalogWatch, but that happens - // on periodic bases. So in order to be sure we got the event we care about - // we wait until the result has a single entry, which means busybox was - // deleted - // + KubernetesCatalogWatch received the new update. - if (result != null && result.size() != 1) { - return false; - } - - // we will only receive one pod here, our own - if (result != null) { - afterDelete[0] = result.get(0); - return true; - } - - return false; - }); - - Assertions.assertTrue(afterDelete[0].endpointName().contains(APP_NAME)); - Assertions.assertEquals("default", afterDelete[0].namespace()); - - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/TestAssertions.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/TestAssertions.java new file mode 100644 index 0000000000..6d679a8918 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/TestAssertions.java @@ -0,0 +1,127 @@ +/* + * 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.catalog.watch; + +import java.time.Duration; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.assertj.core.api.Assertions; +import org.awaitility.Awaitility; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; + +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace; +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.core.ResolvableType; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +/** + * @author wind57 + */ +final class TestAssertions { + + private TestAssertions() { + + } + + static void assertLogStatement(CapturedOutput output, String textToAssert) { + Awaitility.await() + .during(Duration.ofSeconds(5)) + .pollInterval(Duration.ofMillis(200)) + .untilAsserted(() -> Assertions.assertThat(output.getOut()).contains(textToAssert)); + } + + /** + * the checks are the same for both endpoints and endpoint slices, while the set-up + * for them is different. + */ + @SuppressWarnings("unchecked") + static void invokeAndAssert(Util util, Set namespaces, int port, String assertionNamespace) { + + WebClient client = builder().baseUrl("http://localhost:" + port + "/result").build(); + EndpointNameAndNamespace[] holder = new EndpointNameAndNamespace[2]; + ResolvableType resolvableType = ResolvableType.forClassWithGenerics(List.class, EndpointNameAndNamespace.class); + + await().pollInterval(Duration.ofMillis(200)).atMost(Duration.ofSeconds(30)).until(() -> { + List result = (List) client.method(HttpMethod.GET) + .retrieve() + .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())) + .retryWhen(retrySpec()) + .block(); + + if (result != null) { + if (result.size() != 2) { + return false; + } + holder[0] = result.get(0); + holder[1] = result.get(1); + return true; + } + + return false; + }); + + EndpointNameAndNamespace resultOne = holder[0]; + EndpointNameAndNamespace resultTwo = holder[1]; + + assertThat(resultOne).isNotNull(); + assertThat(resultTwo).isNotNull(); + + assertThat(resultOne.endpointName()).contains("busybox"); + assertThat(resultTwo.endpointName()).contains("busybox"); + + assertThat(resultOne.namespace()).isEqualTo(assertionNamespace); + assertThat(resultTwo.namespace()).isEqualTo(assertionNamespace); + + namespaces.forEach(namespace -> util.busybox(namespace, Phase.DELETE)); + + await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(240)).until(() -> { + List result = (List) client.method(HttpMethod.GET) + .retrieve() + .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())) + .retryWhen(retrySpec()) + .block(); + + // we need to get the event from KubernetesCatalogWatch, but that happens + // on periodic bases. So in order to be sure we got the event we care about + // we wait until there is no entry, which means busybox was deleted + // and KubernetesCatalogWatch received that update. + return Objects.requireNonNull(result).isEmpty(); + }); + + } + + 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-catalog-watcher/src/test/resources/app/watcher-endpoints-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-endpoints-deployment.yaml deleted file mode 100644 index fdce297c87..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-endpoints-deployment.yaml +++ /dev/null @@ -1,33 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: spring-cloud-kubernetes-fabric8-client-catalog-watcher -spec: - selector: - matchLabels: - app: spring-cloud-kubernetes-fabric8-client-catalog-watcher - template: - metadata: - labels: - app: spring-cloud-kubernetes-fabric8-client-catalog-watcher - spec: - serviceAccountName: spring-cloud-kubernetes-serviceaccount - containers: - - name: spring-cloud-kubernetes-fabric8-client-catalog-watcher - image: docker.io/springcloud/spring-cloud-kubernetes-fabric8-client-catalog-watcher - imagePullPolicy: IfNotPresent - readinessProbe: - httpGet: - port: 8080 - path: /actuator/health/readiness - livenessProbe: - httpGet: - port: 8080 - path: /actuator/health/liveness - ports: - - containerPort: 8080 - env: - - name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY - value: "DEBUG" - - name: SPRING_CLOUD_KUBERNETES_DISCOVERY_USE_ENDPOINT_SLICES - value: "FALSE" diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-ingress.yaml deleted file mode 100644 index 7b0343edbf..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-ingress.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: spring-cloud-kubernetes-fabric8-client-catalog-watcher-ingress - namespace: default -spec: - rules: - - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: spring-cloud-kubernetes-fabric8-client-catalog-watcher-service - port: - number: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-service.yaml deleted file mode 100644 index 6b9374dcf8..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: spring-cloud-kubernetes-fabric8-client-catalog-watcher-service - name: spring-cloud-kubernetes-fabric8-client-catalog-watcher-service -spec: - ports: - - name: http - port: 8080 - targetPort: 8080 - selector: - app: spring-cloud-kubernetes-fabric8-client-catalog-watcher - type: ClusterIP diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Images.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Images.java index 47021c563f..f37a17ee7e 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Images.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Images.java @@ -79,7 +79,9 @@ public static String wiremockVersion() { } public static void loadBusybox(K3sContainer container) { - Commons.load(container, BUSYBOX_TAR, BUSYBOX, busyboxVersion()); + if (!imageAlreadyInK3s(container, BUSYBOX_TAR)) { + Commons.load(container, BUSYBOX_TAR, BUSYBOX, busyboxVersion()); + } } public static void loadWiremock(K3sContainer container) { @@ -106,6 +108,25 @@ public static void loadRabbitmq(K3sContainer container) { Commons.load(container, RABBITMQ_TAR, RABBITMQ, rabbitMqVersion()); } + private static boolean imageAlreadyInK3s(K3sContainer container, String tarName) { + try { + boolean present = container.execInContainer("sh", "-c", "ctr images list | grep " + tarName) + .getStdout() + .contains(tarName); + if (present) { + System.out.println("image : " + tarName + " already in k3s, skipping"); + return true; + } + else { + System.out.println("image : " + tarName + " not in k3s"); + return false; + } + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + // find the image version from current-images.txt private static String imageVersion(String imageNameForDownload) { BufferedReader reader = new BufferedReader( diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/fabric8_client/Util.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/fabric8_client/Util.java index 0b582812a1..cff24e87ff 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/fabric8_client/Util.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/fabric8_client/Util.java @@ -406,10 +406,12 @@ private void waitForDeploymentToBeDeleted(String namespace, Deployment deploymen Map matchLabels = deployment.getSpec().getSelector().getMatchLabels(); + long start = System.currentTimeMillis(); await().pollInterval(Duration.ofSeconds(1)).atMost(30, TimeUnit.SECONDS).until(() -> { Deployment inner = client.apps().deployments().inNamespace(namespace).withName(deploymentName).get(); return inner == null; }); + System.out.println("Ended in " + (System.currentTimeMillis() - start) + "ms"); await().pollInterval(Duration.ofSeconds(1)).atMost(60, TimeUnit.SECONDS).until(() -> { List podList = client.pods().inNamespace(namespace).withLabels(matchLabels).list().getItems();