diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerIT.java index 9a3bf5e466..909a078db8 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerIT.java @@ -23,31 +23,61 @@ import io.kubernetes.client.openapi.models.V1Deployment; import io.kubernetes.client.openapi.models.V1Ingress; import io.kubernetes.client.openapi.models.V1Service; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.testcontainers.k3s.K3sContainer; import reactor.netty.http.client.HttpClient; import reactor.util.retry.Retry; import reactor.util.retry.RetryBackoffSpec; +import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; import org.springframework.cloud.kubernetes.integration.tests.commons.native_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.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util.patchWithMerge; + /** * @author Ryan Baxter */ +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) class LoadBalancerIT { + private static final BasicJsonTester BASIC_JSON_TESTER = new BasicJsonTester(LoadBalancerIT.class); + + private static final String BODY_FOR_MERGE = """ + { + "spec": { + "template": { + "spec": { + "containers": [{ + "name": "spring-cloud-kubernetes-client-loadbalancer-it", + "env": [ + { + "name": "SPRING_CLOUD_KUBERNETES_LOADBALANCER_MODE", + "value": "SERVICE" + } + ] + }] + } + } + } + } + """; + + private static final Map POD_LABELS = Map.of("app", + "spring-cloud-kubernetes-client-loadbalancer-it"); + private static final String SERVICE_URL = "http://localhost:80/loadbalancer-it/service"; private static final String SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME = "spring-cloud-kubernetes-client-loadbalancer-it"; @@ -65,10 +95,12 @@ static void beforeAll() throws Exception { Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S); util = new Util(K3S); util.setUp(NAMESPACE); + loadbalancerIt(Phase.CREATE); } @AfterAll static void afterAll() throws Exception { + loadbalancerIt(Phase.DELETE); Commons.cleanUp(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S); Commons.systemPrune(); } @@ -84,17 +116,16 @@ void afterEach() { } @Test - void testLoadBalancerServiceMode() { - loadbalancerIt(false, Phase.CREATE); + @Order(1) + void testLoadBalancerPodMode() { testLoadBalancer(); - loadbalancerIt(false, Phase.DELETE); } @Test - void testLoadBalancerPodMode() { - loadbalancerIt(true, Phase.CREATE); + @Order(2) + void testLoadBalancerServiceMode() { + patchForServiceMode("spring-cloud-kubernetes-client-loadbalancer-it-deployment", NAMESPACE); testLoadBalancer(); - loadbalancerIt(true, Phase.DELETE); } private void testLoadBalancer() { @@ -102,21 +133,15 @@ private void testLoadBalancer() { WebClient.Builder builder = builder(); WebClient serviceClient = builder.baseUrl(SERVICE_URL).build(); - ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class); - @SuppressWarnings("unchecked") - Map result = (Map) serviceClient.method(HttpMethod.GET).retrieve() - .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec()) - .block(); - - Assertions.assertTrue(result.containsKey("mappings")); - Assertions.assertTrue(result.containsKey("meta")); - + String result = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).block(); + Assertions.assertThat(BASIC_JSON_TESTER.from(result)).extractingJsonPathArrayValue("$.mappings").isEmpty(); + Assertions.assertThat(BASIC_JSON_TESTER.from(result)).extractingJsonPathNumberValue("$.meta.total") + .isEqualTo(0); } - private void loadbalancerIt(boolean podBased, Phase phase) { - V1Deployment deployment = podBased - ? (V1Deployment) util.yaml("spring-cloud-kubernetes-client-loadbalancer-pod-it-deployment.yaml") - : (V1Deployment) util.yaml("spring-cloud-kubernetes-client-loadbalancer-service-it-deployment.yaml"); + private static void loadbalancerIt(Phase phase) { + V1Deployment deployment = (V1Deployment) util + .yaml("spring-cloud-kubernetes-client-loadbalancer-pod-it-deployment.yaml"); V1Service service = (V1Service) util.yaml("spring-cloud-kubernetes-client-loadbalancer-it-service.yaml"); V1Ingress ingress = (V1Ingress) util.yaml("spring-cloud-kubernetes-client-loadbalancer-it-ingress.yaml"); @@ -136,4 +161,8 @@ private RetryBackoffSpec retrySpec() { return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); } + private static void patchForServiceMode(String deploymentName, String namespace) { + patchWithMerge(deploymentName, namespace, BODY_FOR_MERGE, POD_LABELS); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/spring-cloud-kubernetes-client-loadbalancer-service-it-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/spring-cloud-kubernetes-client-loadbalancer-service-it-deployment.yaml deleted file mode 100644 index 3024a4fe82..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/spring-cloud-kubernetes-client-loadbalancer-service-it-deployment.yaml +++ /dev/null @@ -1,31 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: spring-cloud-kubernetes-client-loadbalancer-it-deployment -spec: - selector: - matchLabels: - app: spring-cloud-kubernetes-client-loadbalancer-it - template: - metadata: - labels: - app: spring-cloud-kubernetes-client-loadbalancer-it - spec: - serviceAccountName: spring-cloud-kubernetes-serviceaccount - containers: - - name: spring-cloud-kubernetes-client-loadbalancer-it - env: - - name: SPRING_CLOUD_KUBERNETES_LOADBALANCER_MODE - value: SERVICE - image: docker.io/springcloud/spring-cloud-kubernetes-client-loadbalancer-it - imagePullPolicy: IfNotPresent - readinessProbe: - httpGet: - port: 8080 - path: /actuator/health/readiness - livenessProbe: - httpGet: - port: 8080 - path: /actuator/health/liveness - ports: - - containerPort: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientFilterNamespaceDelegate.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientFilterNamespaceDelegate.java new file mode 100644 index 0000000000..84794e28d7 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientFilterNamespaceDelegate.java @@ -0,0 +1,87 @@ +/* + * 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.discoveryclient.it; + +import java.time.Duration; +import java.util.Objects; + +import org.assertj.core.api.Assertions; +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.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * @author mbialkowski1 + */ +final class DiscoveryClientFilterNamespaceDelegate { + + private static final BasicJsonTester BASIC_JSON_TESTER = new BasicJsonTester( + DiscoveryClientFilterNamespaceDelegate.class); + + static void testNamespaceDiscoveryClient() { + testLoadBalancer(); + testHealth(); + } + + private static void testLoadBalancer() { + + WebClient.Builder builder = builder(); + WebClient serviceClient = builder.baseUrl("http://localhost:80/discoveryclient-it/services").build(); + String result = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) + .block(); + + Assertions.assertThat(BASIC_JSON_TESTER.from(result)).extractingJsonPathArrayValue("$") + .contains("service-wiremock"); + + // ServiceInstance + WebClient serviceInstanceClient = builder + .baseUrl("http://localhost:80/discoveryclient-it/service/service-wiremock").build(); + String serviceInstances = serviceInstanceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class) + .retryWhen(retrySpec()).block(); + + Assertions.assertThat(BASIC_JSON_TESTER.from(serviceInstances)).extractingJsonPathStringValue("$.[0].serviceId") + .isEqualTo("service-wiremock"); + + Assertions.assertThat(BASIC_JSON_TESTER.from(serviceInstances)).extractingJsonPathStringValue("$.[0].namespace") + .isEqualTo("left"); + } + + private static void testHealth() { + WebClient.Builder builder = builder(); + WebClient serviceClient = builder.baseUrl("http://localhost:80/discoveryclient-it/actuator/health").build(); + + String health = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) + .block(); + + Assertions.assertThat(BASIC_JSON_TESTER.from(health)) + .extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP"); + } + + 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-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java index ac96344254..7f4903717e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java @@ -17,13 +17,15 @@ package org.springframework.cloud.kubernetes.discoveryclient.it; import java.time.Duration; -import java.util.Arrays; import java.util.Map; import java.util.Objects; +import io.kubernetes.client.openapi.apis.RbacAuthorizationV1Api; +import io.kubernetes.client.openapi.models.V1ClusterRoleBinding; import io.kubernetes.client.openapi.models.V1Deployment; import io.kubernetes.client.openapi.models.V1Ingress; import io.kubernetes.client.openapi.models.V1Service; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -32,28 +34,81 @@ import reactor.util.retry.Retry; import reactor.util.retry.RetryBackoffSpec; +import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.Phase; import org.springframework.cloud.kubernetes.integration.tests.commons.native_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.springframework.cloud.kubernetes.discoveryclient.it.DiscoveryClientFilterNamespaceDelegate.testNamespaceDiscoveryClient; +import static org.springframework.cloud.kubernetes.integration.tests.commons.native_client.Util.patchWithReplace; /** * @author Ryan Baxter */ class DiscoveryClientIT { + private static final String BODY_ONE = """ + { + "spec": { + "template": { + "spec": { + "containers": [{ + "name": "spring-cloud-kubernetes-discoveryclient-it", + "image": "image_name_here", + "env": [ + { + "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0", + "value": "left" + } + ] + }] + } + } + } + } + """; + + private static final String BODY_TWO = """ + { + "spec": { + "template": { + "spec": { + "containers": [{ + "name": "spring-cloud-kubernetes-discoveryserver", + "image": "image_name_here", + "env": [ + { + "name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_ALL_NAMESPACES", + "value": "TRUE" + } + ] + }] + } + } + } + } + """; + + private static final Map POD_LABELS = Map.of("app", "spring-cloud-kubernetes-discoveryclient-it"); + + private static final Map POD_LABELS_DISCOVERY = Map.of("app", + "spring-cloud-kubernetes-discoveryserver"); + + private static final BasicJsonTester BASIC_JSON_TESTER = new BasicJsonTester(DiscoveryClientIT.class); + private static final String DISCOVERY_SERVER_APP_NAME = "spring-cloud-kubernetes-discoveryserver"; private static final String SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME = "spring-cloud-kubernetes-discoveryclient-it"; private static final String NAMESPACE = "default"; + private static final String NAMESPACE_LEFT = "left"; + + private static final String NAMESPACE_RIGHT = "right"; + private static final K3sContainer K3S = Commons.container(); private static Util util; @@ -67,8 +122,21 @@ static void beforeAll() throws Exception { Commons.validateImage(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, K3S); Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, K3S); + util = new Util(K3S); util.setUp(NAMESPACE); + + util.createNamespace(NAMESPACE_LEFT); + util.createNamespace(NAMESPACE_RIGHT); + + RbacAuthorizationV1Api rbacApi = new RbacAuthorizationV1Api(); + V1ClusterRoleBinding clusterRole = (V1ClusterRoleBinding) util + .yaml("namespace-filter/cluster-admin-serviceaccount-role.yaml"); + rbacApi.createClusterRoleBinding(clusterRole, null, null, null, null); + + util.wiremock(NAMESPACE_LEFT, "/wiremock-" + NAMESPACE_LEFT, Phase.CREATE, false); + util.wiremock(NAMESPACE_RIGHT, "/wiremock-" + NAMESPACE_RIGHT, Phase.CREATE, false); + discoveryServer(Phase.CREATE); } @@ -78,6 +146,12 @@ static void afterAll() throws Exception { Commons.cleanUp(DISCOVERY_SERVER_APP_NAME, K3S); Commons.cleanUp(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, K3S); + util.wiremock(NAMESPACE_LEFT, "/wiremock-" + NAMESPACE_LEFT, Phase.DELETE, false); + util.wiremock(NAMESPACE_RIGHT, "/wiremock-" + NAMESPACE_RIGHT, Phase.DELETE, false); + + util.deleteNamespace(NAMESPACE_LEFT); + util.deleteNamespace(NAMESPACE_RIGHT); + discoveryServer(Phase.DELETE); discoveryIt(Phase.DELETE); Commons.systemPrune(); @@ -88,34 +162,35 @@ void testDiscoveryClient() { discoveryIt(Phase.CREATE); testLoadBalancer(); testHealth(); + + patchForNamespaceFilter( + "docker.io/springcloud/spring-cloud-kubernetes-discoveryclient-it:" + Commons.pomVersion(), + "spring-cloud-kubernetes-discoveryclient-it-deployment", NAMESPACE); + patchForAllNamespaces("docker.io/springcloud/spring-cloud-kubernetes-discoveryserver:" + Commons.pomVersion(), + "spring-cloud-kubernetes-discoveryserver-deployment", NAMESPACE); + testNamespaceDiscoveryClient(); } private void testLoadBalancer() { WebClient.Builder builder = builder(); WebClient serviceClient = builder.baseUrl("http://localhost:80/discoveryclient-it/services").build(); - String[] result = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String[].class) - .retryWhen(retrySpec()).block(); - assertThat(Arrays.stream(result).anyMatch("spring-cloud-kubernetes-discoveryserver"::equalsIgnoreCase)) - .isTrue(); + String result = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) + .block(); + Assertions.assertThat(BASIC_JSON_TESTER.from(result)).extractingJsonPathArrayValue("$") + .contains("spring-cloud-kubernetes-discoveryserver"); } - @SuppressWarnings("unchecked") void testHealth() { WebClient.Builder builder = builder(); WebClient serviceClient = builder.baseUrl("http://localhost:80/discoveryclient-it/actuator/health").build(); - ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class); - @SuppressWarnings("unchecked") - Map health = (Map) serviceClient.method(HttpMethod.GET).retrieve() - .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec()) + String health = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) .block(); - Map components = (Map) health.get("components"); - - Map discoveryComposite = (Map) components.get("discoveryComposite"); - assertThat(discoveryComposite.get("status")).isEqualTo("UP"); + Assertions.assertThat(BASIC_JSON_TESTER.from(health)) + .extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP"); } private static void discoveryIt(Phase phase) { @@ -154,4 +229,12 @@ private RetryBackoffSpec retrySpec() { return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); } + static void patchForNamespaceFilter(String image, String deploymentName, String namespace) { + patchWithReplace(image, deploymentName, namespace, BODY_ONE, POD_LABELS); + } + + static void patchForAllNamespaces(String image, String deploymentName, String namespace) { + patchWithReplace(image, deploymentName, namespace, BODY_TWO, POD_LABELS_DISCOVERY); + } + } diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/native_client/Util.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/native_client/Util.java index 88f72cc8de..b55751800f 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/native_client/Util.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/native_client/Util.java @@ -420,9 +420,6 @@ public void deleteNamespace(String name) { .getItems().stream().noneMatch(x -> x.getMetadata().getName().equals(name))); } - /** - * deploy wiremock without ingress. - */ public void wiremock(String namespace, String path, Phase phase) { wiremock(namespace, path, phase, true); }