diff --git a/docs/src/main/asciidoc/discovery-client.adoc b/docs/src/main/asciidoc/discovery-client.adoc index cb49eac9..f5132639 100644 --- a/docs/src/main/asciidoc/discovery-client.adoc +++ b/docs/src/main/asciidoc/discovery-client.adoc @@ -117,5 +117,15 @@ this to work, you need to align the Kubernetes service name with the `spring.app NOTE: `spring.application.name` has no effect as far as the name registered for the application within Kubernetes Spring Cloud Kubernetes can also watch the Kubernetes service catalog for changes and update the -`DiscoveryClient` implementation accordingly. In order to enable this functionality you need to add +`DiscoveryClient` implementation accordingly. By "watch" we mean that we will publish a heartbeat event every `spring.cloud.kubernetes.discovery.catalog-services-watch-delay` +milliseconds (by default it is `30000`). The heartbeat event will contain the target references (and their namespaces of the addresses of all endpoints +(for the exact details of what will get returned you can take a look inside `KubernetesCatalogWatch`). This is an implementation detail, and listeners of the heartbeat event +should not rely on the details. Instead, they should see if there are differences between two subsequent heartbeats via `equals` method. We will take care to return a correct implementation that adheres to the equals contract. +The endpoints will be queried in either all namespaces (enabled via `spring.cloud.kubernetes.discovery.all-namespaces=true`), or +we will use: xref:property-source-config.adoc#namespace-resolution[Namespace Resolution]. + + + + +In order to enable this functionality you need to add `@EnableScheduling` on a configuration class in your application. diff --git a/istio-cli/istio-1.13.3/bin/istioctl b/istio-cli/istio-1.16.0/bin/istioctl similarity index 81% rename from istio-cli/istio-1.13.3/bin/istioctl rename to istio-cli/istio-1.16.0/bin/istioctl index 0319896a..65494bd8 100755 Binary files a/istio-cli/istio-1.13.3/bin/istioctl and b/istio-cli/istio-1.16.0/bin/istioctl differ diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatch.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatch.java index 20ead0d8..90b8d556 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatch.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesCatalogWatch.java @@ -85,13 +85,15 @@ public class KubernetesCatalogWatch implements ApplicationEventPublisherAware { .list().getItems(); } - /* - *
 - An "Endpoints" holds a List of EndpointSubset. - A single
-			 * EndpointSubset holds a List of EndpointAddress
+			/**
+			 * 
+			 *   - An "Endpoints" holds a List of EndpointSubset.
+			 *   - A single EndpointSubset holds a List of EndpointAddress
 			 *
-			 * - (The union of all EndpointSubsets is the Set of all Endpoints) - Set of
-			 * Endpoints is the cartesian product of : EndpointSubset::getAddresses and
-			 * EndpointSubset::getPorts (each is a List) 
+ * - (The union of all EndpointSubsets is the Set of all Endpoints) + * - Set of Endpoints is the cartesian product of : + * EndpointSubset::getAddresses and EndpointSubset::getPorts (each is a List) + *
*/ List currentState = endpoints.stream().map(Endpoints::getSubsets) .filter(Objects::nonNull).flatMap(List::stream).map(EndpointSubset::getAddresses) diff --git a/spring-cloud-kubernetes-integration-tests/pom.xml b/spring-cloud-kubernetes-integration-tests/pom.xml index c3597f1b..5c86ae63 100644 --- a/spring-cloud-kubernetes-integration-tests/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/pom.xml @@ -90,5 +90,6 @@ spring-cloud-kubernetes-client-configmap-event-reload spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps + spring-cloud-kubernetes-fabric8-client-catalog-watcher 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 new file mode 100644 index 00000000..d7f82013 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/pom.xml @@ -0,0 +1,115 @@ + + + + spring-cloud-kubernetes-integration-tests + org.springframework.cloud + 3.0.0-SNAPSHOT + + 4.0.0 + + spring-cloud-kubernetes-fabric8-client-catalog-watcher + + + + org.springframework.cloud + spring-cloud-kubernetes-fabric8-discovery + + + org.springframework.cloud + spring-cloud-kubernetes-test-support + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-actuator + + + com.github.docker-java + docker-java-core + test + + + com.github.docker-java + docker-java-transport-httpclient5 + test + + + + + + + ../src/main/resources + true + + + src/main/resources + true + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + build-image + + ${skip.build.image} + + package + + build-image + + + + repackage + package + + repackage + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Application.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Application.java new file mode 100644 index 00000000..a3062c09 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/Application.java @@ -0,0 +1,34 @@ +/* + * 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 org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * @author wind57 + */ +@SpringBootApplication +@EnableScheduling +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/EndpointNameAndNamespaceService.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/EndpointNameAndNamespaceService.java new file mode 100644 index 00000000..c4a3e86d --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/EndpointNameAndNamespaceService.java @@ -0,0 +1,43 @@ +/* + * 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.util.List; + +import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace; +import org.springframework.stereotype.Service; + +/** + * holds an EndpointNameAndNamespace object, needed so that the controller can poll to see + * if anything has changed. And we call the controller from our tests. + * + * @author wind57 + */ +@Service +public class EndpointNameAndNamespaceService { + + private List result; + + public List result() { + return result; + } + + public void setResult(List result) { + this.result = result; + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartBeatListener.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartBeatListener.java new file mode 100644 index 00000000..926c7b7f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartBeatListener.java @@ -0,0 +1,51 @@ +/* + * 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.util.List; + +import org.springframework.cloud.client.discovery.event.HeartbeatEvent; +import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * Listener that will catch events from + * {@link org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesCatalogWatch} + * + * @author wind57 + */ +@Component +public class HeartBeatListener implements ApplicationListener { + + private final EndpointNameAndNamespaceService service; + + public HeartBeatListener(EndpointNameAndNamespaceService service) { + this.service = service; + } + + @SuppressWarnings("unchecked") + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof HeartbeatEvent heartbeatEvent) { + List result = (List) heartbeatEvent.getValue(); + service.setResult(result); + } + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartbeatController.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartbeatController.java new file mode 100644 index 00000000..f2889faa --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/main/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/HeartbeatController.java @@ -0,0 +1,39 @@ +/* + * 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.util.List; + +import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HeartbeatController { + + private final EndpointNameAndNamespaceService service; + + public HeartbeatController(EndpointNameAndNamespaceService service) { + this.service = service; + } + + @GetMapping("/result") + public List result() { + return service.result(); + } + +} 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/CatalogWatchIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/java/org/springframework/cloud/kubernetes/fabric8/catalog/watch/CatalogWatchIT.java new file mode 100644 index 00000000..18305b3b --- /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/CatalogWatchIT.java @@ -0,0 +1,236 @@ +/* + * 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.Objects; + +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.Config; +import io.fabric8.kubernetes.client.DefaultKubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; + +import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.Fabric8Utils; +import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; +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.awaitility.Awaitility.await; + +/** + * @author wind57 + */ +class CatalogWatchIT { + + private static final String APP_NAME = "spring-cloud-kubernetes-fabric8-client-catalog-watcher"; + + private static final String NAMESPACE = "default"; + + private static final K3sContainer K3S = Commons.container(); + + private static KubernetesClient client; + + private String busyboxServiceName; + + private String busyboxDeploymentName; + + private String appDeploymentName; + + private String appServiceName; + + private String appIngressName; + + @BeforeAll + static void beforeAll() { + K3S.start(); + Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml()); + client = new DefaultKubernetesClient(config); + } + + /** + *
+	 *     - 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
+	 * 
+ */ + @SuppressWarnings("unchecked") + @Test + void testCatalogWatch() throws Exception { + + Commons.validateImage(APP_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(APP_NAME, K3S); + + Fabric8Utils.setUp(client, "default"); + + deployBusyboxManifests(); + deployApp(); + + WebClient client = builder().baseUrl("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("default", resultOne.namespace()); + Assertions.assertEquals("default", resultTwo.namespace()); + + deleteBusyboxApp(); + + // 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 will only receive one pod here, our own + if (result != null) { + Assertions.assertEquals(1, result.size()); + afterDelete[0] = result.get(0); + return true; + } + + return false; + }); + + Assertions.assertTrue(afterDelete[0].endpointName().contains(APP_NAME)); + Assertions.assertEquals("default", afterDelete[0].namespace()); + + deleteApp(); + + } + + private void deployBusyboxManifests() { + + Deployment deployment = client.apps().deployments().load(getBusyboxDeployment()).get(); + client.apps().deployments().inNamespace(NAMESPACE).create(deployment); + busyboxDeploymentName = deployment.getMetadata().getName(); + + Service busyboxService = client.services().load(getBusyboxService()).get(); + busyboxServiceName = busyboxService.getMetadata().getName(); + client.services().inNamespace(NAMESPACE).create(busyboxService); + + Fabric8Utils.waitForDeployment(client, busyboxDeploymentName, NAMESPACE, 2, 600); + + } + + private void deployApp() { + + Deployment appDeployment = client.apps().deployments().load(getAppDeployment()).get(); + + String version = K8SUtils.getPomVersion(); + String currentImage = appDeployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage(); + appDeployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(currentImage + ":" + version); + + client.apps().deployments().inNamespace(NAMESPACE).create(appDeployment); + appDeploymentName = appDeployment.getMetadata().getName(); + + Service appService = client.services().load(getAppService()).get(); + appServiceName = appService.getMetadata().getName(); + client.services().inNamespace(NAMESPACE).create(appService); + + Fabric8Utils.waitForDeployment(client, appDeploymentName, NAMESPACE, 2, 600); + + Ingress appIngress = client.network().v1().ingresses().load(getAppIngress()).get(); + appIngressName = appIngress.getMetadata().getName(); + client.network().v1().ingresses().inNamespace(NAMESPACE).create(appIngress); + + Fabric8Utils.waitForIngress(client, appIngressName, NAMESPACE); + + } + + private void deleteBusyboxApp() { + Fabric8Utils.deleteDeployment(client, NAMESPACE, busyboxDeploymentName); + Fabric8Utils.deleteService(client, NAMESPACE, busyboxServiceName); + } + + private void deleteApp() { + Fabric8Utils.deleteDeployment(client, NAMESPACE, appDeploymentName); + Fabric8Utils.deleteService(client, NAMESPACE, appServiceName); + client.network().v1().ingresses().withName(appIngressName).delete(); + } + + private static InputStream getBusyboxService() { + return Fabric8Utils.inputStream("busybox/service.yaml"); + } + + private static InputStream getBusyboxDeployment() { + return Fabric8Utils.inputStream("busybox/deployment.yaml"); + } + + private static InputStream getAppDeployment() { + return Fabric8Utils.inputStream("app/watcher-deployment.yaml"); + } + + private static InputStream getAppIngress() { + return Fabric8Utils.inputStream("app/watcher-ingress.yaml"); + } + + private static InputStream getAppService() { + return Fabric8Utils.inputStream("app/watcher-service.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); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-deployment.yaml new file mode 100644 index 00000000..a8ae8920 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-deployment.yaml @@ -0,0 +1,31 @@ +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 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 new file mode 100644 index 00000000..7b0343ed --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-ingress.yaml @@ -0,0 +1,16 @@ +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 new file mode 100644 index 00000000..6b9374dc --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/app/watcher-service.yaml @@ -0,0 +1,14 @@ +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-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/busybox/deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/busybox/deployment.yaml new file mode 100644 index 00000000..75dd2268 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/busybox/deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: busybox +spec: + selector: + matchLabels: + app: busybox + version: v1 + replicas: 2 + template: + metadata: + labels: + app: busybox + version: v1 + spec: + containers: + - name: busybox + # image: arm64/busybox:latest + image: busybox:latest + command: ["/bin/sh"] + args: ["-c", "sleep 100000"] diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/busybox/service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/busybox/service.yaml new file mode 100644 index 00000000..7a23a4d9 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/busybox/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: busybox-service +spec: + selector: + app: busybox + type: ClusterIP + ports: + - name: busybox-port + port: 8080 + targetPort: 80 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9e284876 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-catalog-watcher/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/Fabric8IstioIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/Fabric8IstioIT.java index 9f2fbebf..0af50c41 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/Fabric8IstioIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/Fabric8IstioIT.java @@ -59,7 +59,7 @@ class Fabric8IstioIT { private static final String ISTIO_PILOT = "istio/pilot"; - private static final String ISTIO_VERSION = "1.13.3"; + private static final String ISTIO_VERSION = "1.16.0"; private static final String LOCAL_ISTIO_BIN_PATH = "../../istio-cli/istio-" + ISTIO_VERSION + "/bin"; diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java index fb913376..9d9e21ba 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java @@ -46,7 +46,7 @@ public final class Commons { /** * Rancher version to use for test-containers. */ - public static final String RANCHER = "rancher/k3s:v1.24.2-k3s2"; + public static final String RANCHER = "rancher/k3s:v1.25.4-k3s1"; /** * Command to use when starting rancher. Without "server" option, traefik is not diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java index ecf2de17..1b07b018 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java @@ -19,12 +19,15 @@ package org.springframework.cloud.kubernetes.integration.tests.commons; import java.io.InputStream; import java.time.Duration; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Endpoints; import io.fabric8.kubernetes.api.model.LoadBalancerIngress; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceAccount; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; @@ -174,6 +177,42 @@ public final class Fabric8Utils { }); } + /** + * delete a deployment and every pod by spec.select.matchLabels, waits until + * everything is deleted. + */ + public static void deleteDeployment(KubernetesClient client, String namespace, String name) { + + Deployment deployment = client.apps().deployments().inNamespace(namespace).withName(name).get(); + Map matchLabels = deployment.getSpec().getSelector().getMatchLabels(); + + client.apps().deployments().inNamespace(namespace).delete(deployment); + + await().pollInterval(Duration.ofSeconds(1)).atMost(30, TimeUnit.SECONDS).until(() -> { + Deployment inner = client.apps().deployments().inNamespace(namespace).withName(name).get(); + return inner == null; + }); + + await().pollInterval(Duration.ofSeconds(1)).atMost(60, TimeUnit.SECONDS).until(() -> { + List podList = client.pods().inNamespace(namespace).withLabels(matchLabels).list().getItems(); + return podList == null || podList.isEmpty(); + }); + + } + + /** + * delete the service and wait for it to be deleted. + */ + public static void deleteService(KubernetesClient client, String namespace, String name) { + + client.services().inNamespace(namespace).withName(name).delete(); + + await().pollInterval(Duration.ofSeconds(1)).atMost(30, TimeUnit.SECONDS).until(() -> { + Service service = client.services().inNamespace(namespace).withName(name).get(); + return service == null; + }); + } + private static void innerSetup(KubernetesClient client, String namespace, InputStream serviceAccountAsStream, InputStream roleBindingAsStream, InputStream roleAsStream) { ServiceAccount serviceAccountFromStream = client.serviceAccounts().load(serviceAccountAsStream).get();