Add integration test for fabric8 catalog watcher (#1147)
This commit is contained in:
@@ -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.
|
||||
|
||||
Binary file not shown.
@@ -85,13 +85,15 @@ public class KubernetesCatalogWatch implements ApplicationEventPublisherAware {
|
||||
.list().getItems();
|
||||
}
|
||||
|
||||
/*
|
||||
* <pre> - An "Endpoints" holds a List of EndpointSubset. - A single
|
||||
* EndpointSubset holds a List of EndpointAddress
|
||||
/**
|
||||
* <pre>
|
||||
* - 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) </pre>
|
||||
* - (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)
|
||||
* </pre>
|
||||
*/
|
||||
List<EndpointNameAndNamespace> currentState = endpoints.stream().map(Endpoints::getSubsets)
|
||||
.filter(Objects::nonNull).flatMap(List::stream).map(EndpointSubset::getAddresses)
|
||||
|
||||
@@ -90,5 +90,6 @@
|
||||
<module>spring-cloud-kubernetes-client-configmap-event-reload</module>
|
||||
<module>spring-cloud-kubernetes-client-configmap-event-reload-multiple-apps</module>
|
||||
<module>spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps</module>
|
||||
<module>spring-cloud-kubernetes-fabric8-client-catalog-watcher</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-fabric8-client-catalog-watcher</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-fabric8-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-transport-httpclient5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>../src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<!-- build image in the 'package' phase, and ignore plain tests -->
|
||||
<!-- via maven-surefire-plugin::skipTests -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-image</id>
|
||||
<configuration>
|
||||
<skip>${skip.build.image}</skip>
|
||||
</configuration>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>repackage</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- run tests in the 'integration-tests' phase, one that is after 'package' (where we build the image) -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<EndpointNameAndNamespace> result;
|
||||
|
||||
public List<EndpointNameAndNamespace> result() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(List<EndpointNameAndNamespace> result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ApplicationEvent> {
|
||||
|
||||
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<EndpointNameAndNamespace> result = (List<EndpointNameAndNamespace>) heartbeatEvent.getValue();
|
||||
service.setResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<EndpointNameAndNamespace> result() {
|
||||
return service.result();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - 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
|
||||
* </pre>
|
||||
*/
|
||||
@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<EndpointNameAndNamespace> result = (List<EndpointNameAndNamespace>) 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<EndpointNameAndNamespace> result = (List<EndpointNameAndNamespace>) 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String, String> 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<Pod> 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();
|
||||
|
||||
Reference in New Issue
Block a user