Add integration test for fabric8 catalog watcher (#1147)

This commit is contained in:
erabii
2022-11-28 03:11:53 +02:00
committed by GitHub
parent 3152fd25f5
commit 27bdbb5ddc
19 changed files with 688 additions and 9 deletions

View File

@@ -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.

View File

@@ -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)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"]

View File

@@ -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

View File

@@ -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>

View File

@@ -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";

View File

@@ -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

View File

@@ -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();