Initial commit

This commit is contained in:
spencergibb
2023-09-19 13:10:02 -04:00
parent 2e4d529f30
commit 8a57c69c88
1303 changed files with 8661 additions and 95772 deletions

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes</artifactId>
<version>3.0.4-SNAPSHOT</version>
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
<version>3.0.4-SNAPSHOT</version>
<description>Spring Cloud parent pom, managing plugins and dependencies for Spring
Cloud projects</description>
<url>https://cloud.spring.io/spring-cloud-kubernetes-test-support</url>
<inceptionYear>2017</inceptionYear>
<organization>
<name>Pivotal Software, Inc.</name>
<url>https://www.spring.io</url>
</organization>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dsyer</id>
<name>Dave Syer</name>
<email>dsyer at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
<developer>
<id>sgibb</id>
<name>Spencer Gibb</name>
<email>sgibb at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
<developer>
<id>mgrzejszczak</id>
<name>Marcin Grzejszczak</name>
<email>mgrzejszczak at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
<developer>
<id>rbaxter</id>
<name>Ryan Baxter</name>
<email>rbaxter at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
<developer>
<id>omaciaszeksharma</id>
<name>Olga Maciaszek-Sharma</name>
<email>omaciaszeksharma at pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
<roles>
<role>developer</role>
</roles>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-cloud/spring-cloud-kubernetes.git/spring-cloud-kubernetes-test-support</connection>
<developerConnection>scm:git:ssh://git@github.com/spring-cloud/spring-cloud-kubernetes.git/spring-cloud-kubernetes-test-support</developerConnection>
<url>https://github.com/spring-cloud/spring-cloud-kubernetes/spring-cloud-kubernetes-test-support</url>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.0.7</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java-extended</artifactId>
<version>17.0.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>6.2.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.0.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>k3s</artifactId>
<version>1.18.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,81 +0,0 @@
<?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>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
<properties>
<awaitility.version>4.0.3</awaitility.version>
<testcontainers.version>1.19.0</testcontainers.version>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java-extended</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>${awaitility.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>k3s</artifactId>
<version>${testcontainers.version}</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>run-on-github-actions</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>org.springframework.cloud.kubernetes.tests.discovery.TestsDiscovery</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -1,278 +0,0 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.integration.tests.commons;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.github.dockerjava.api.command.ListImagesCmd;
import com.github.dockerjava.api.command.PullImageCmd;
import com.github.dockerjava.api.command.SaveImageCmd;
import com.github.dockerjava.api.model.Image;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.Assertions;
import org.testcontainers.containers.Container;
import org.testcontainers.k3s.K3sContainer;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.MountableFile;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import static org.awaitility.Awaitility.await;
/**
* A few commons things that can be re-used across clients. This is meant to be used for
* testing purposes only.
*
* @author wind57
*/
public final class Commons {
private static final Log LOG = LogFactory.getLog(Commons.class);
/**
* istio version used in our integration tests.
*/
public static final String ISTIO_VERSION = "1.16.0";
private static final String LOCAL_ISTIO_BIN_PATH = "istio-cli/istio-" + ISTIO_VERSION + "/bin";
private Commons() {
throw new AssertionError("No instance provided");
}
private static final String KUBERNETES_VERSION_FILE = "META-INF/springcloudkubernetes-version.txt";
/**
* Rancher version to use for test-containers.
*/
public static final String RANCHER = "rancher/k3s:v1.25.4-k3s1";
/**
* Command to use when starting rancher. Without "server" option, traefik is not
* installed
*/
public static final String RANCHER_COMMAND = "server";
/**
* Test containers exposed ports.
*/
public static final int[] EXPOSED_PORTS = new int[] { 80, 6443, 8080, 8888, 9092 };
/**
* Temporary folder where to load images.
*/
public static final String TEMP_FOLDER = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath();
private static final K3sContainer CONTAINER = new FixedPortsK3sContainer(DockerImageName.parse(Commons.RANCHER))
.configureFixedPorts(EXPOSED_PORTS).withFileSystemBind(TEMP_FOLDER, TEMP_FOLDER)
.withCopyFileToContainer(MountableFile.forClasspathResource(LOCAL_ISTIO_BIN_PATH + "/istioctl", 0744),
"/tmp/istioctl")
.withCommand(Commons.RANCHER_COMMAND).withReuse(true);
public static K3sContainer container() {
return CONTAINER;
}
public static void loadSpringCloudKubernetesImage(String project, K3sContainer container) throws Exception {
loadImage("springcloud/" + project, pomVersion(), project, container);
}
/**
* assert that "left" is present and if so, "right" is not.
*/
public static void assertReloadLogStatements(String left, String right, String appLabel) {
try {
String appPodName = CONTAINER.execInContainer("sh", "-c",
"kubectl get pods -l app=" + appLabel + " -o=name --no-headers | tr -d '\n'").getStdout();
LOG.info("appPodName : ->" + appPodName + "<-");
// we issue a pollDelay to let the logs sync in, otherwise the results are not
// going to be correctly asserted
await().pollDelay(20, TimeUnit.SECONDS).pollInterval(Duration.ofSeconds(5)).atMost(Duration.ofSeconds(600))
.until(() -> {
Container.ExecResult result = CONTAINER.execInContainer("sh", "-c",
"kubectl logs " + appPodName.trim() + "| grep " + "'" + left + "'");
String error = result.getStderr();
String ok = result.getStdout();
LOG.info("error is : -->" + error + "<--");
if (ok != null && !ok.isBlank()) {
if (!right.isBlank()) {
String notPresent = CONTAINER
.execInContainer("sh", "-c",
"kubectl logs " + appPodName.trim() + "| grep " + "'" + right + "'")
.getStdout();
Assertions.assertTrue(notPresent == null || notPresent.isBlank());
}
return true;
}
LOG.info("log statement not yet present");
return false;
});
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void loadImage(String image, String tag, String tarName, K3sContainer container) throws Exception {
// save image
try (SaveImageCmd saveImageCmd = container.getDockerClient().saveImageCmd(image)) {
InputStream imageStream = saveImageCmd.withTag(tag).exec();
Path imagePath = Paths.get(TEMP_FOLDER + "/" + tarName + ".tar");
Files.deleteIfExists(imagePath);
Files.copy(imageStream, imagePath);
// import image with ctr. this works because TEMP_FOLDER is mounted in the
// container
container.execInContainer("ctr", "i", "import", TEMP_FOLDER + "/" + tarName + ".tar");
}
}
public static void cleanUp(String image, K3sContainer container) throws Exception {
container.execInContainer("crictl", "rmi", "docker.io/springcloud/" + image + ":" + pomVersion());
container.execInContainer("rm", TEMP_FOLDER + "/" + image + ".tar");
}
public static void cleanUpDownloadedImage(String image) throws Exception {
CONTAINER.execInContainer("crictl", "rmi", image);
}
/**
* validates that the provided image does exist in the local docker registry.
*/
public static void validateImage(String image, K3sContainer container) {
try (ListImagesCmd listImagesCmd = container.getDockerClient().listImagesCmd()) {
List<Image> images = listImagesCmd.exec();
images.stream()
.filter(x -> Arrays.stream(x.getRepoTags() == null ? new String[] {} : x.getRepoTags())
.anyMatch(y -> y.contains(image)))
.findFirst().orElseThrow(() -> new IllegalArgumentException("Image : " + image
+ " not build locally. " + "You need to build it first, and then run the test"));
}
}
public static void pullImage(String image, String tag, K3sContainer container) throws InterruptedException {
try (PullImageCmd pullImageCmd = container.getDockerClient().pullImageCmd(image)) {
pullImageCmd.withTag(tag).start().awaitCompletion();
}
}
public static String processExecResult(Container.ExecResult execResult) {
if (execResult.getExitCode() != 0) {
throw new RuntimeException("stdout=" + execResult.getStdout() + "\n" + "stderr=" + execResult.getStderr());
}
return execResult.getStdout();
}
/**
* equivalent of 'docker system prune', but for crictl.
*/
public static void systemPrune() {
try {
CONTAINER.execInContainer("sh", "-c",
"crictl ps -a | grep -v Running | awk '{print $1}' | xargs crictl rm && crictl rmi --prune");
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String pomVersion() {
try (InputStream in = new ClassPathResource(KUBERNETES_VERSION_FILE).getInputStream()) {
String version = StreamUtils.copyToString(in, StandardCharsets.UTF_8);
if (StringUtils.hasText(version)) {
version = version.trim();
}
return version;
}
catch (IOException e) {
ReflectionUtils.rethrowRuntimeException(e);
}
// not reachable since exception rethrown at runtime
return null;
}
/**
* the assumption is that there is only a single pod that is 'Running'.
*/
public static void waitForLogStatement(String message, K3sContainer k3sContainer, String imageName) {
try {
await().atMost(Duration.ofMinutes(2)).pollInterval(Duration.ofSeconds(4)).until(() -> {
String appPodName = k3sContainer.execInContainer("sh", "-c",
"kubectl get pods -l app=" + imageName
+ " -o custom-columns=POD:metadata.name,STATUS:status.phase"
+ " | grep -i 'running' | awk '{print $1}' | tr -d '\n' ")
.getStdout();
String execResult = k3sContainer.execInContainer("sh", "-c", "kubectl logs " + appPodName.trim())
.getStdout();
return execResult.contains(message);
});
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* A K3sContainer, but with fixed port mappings. This is needed because of the nature
* of some integration tests.
*
* @author wind57
*/
private static final class FixedPortsK3sContainer extends K3sContainer {
private FixedPortsK3sContainer(DockerImageName dockerImageName) {
super(dockerImageName);
}
private FixedPortsK3sContainer configureFixedPorts(int[] ports) {
for (int port : ports) {
super.addFixedExposedPort(port, port);
}
return this;
}
}
}

View File

@@ -1,484 +0,0 @@
/*
* Copyright 2013-2020 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.integration.tests.commons;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.apis.AppsV1Api;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
import io.kubernetes.client.openapi.apis.RbacAuthorizationV1Api;
import io.kubernetes.client.openapi.models.V1ClusterRole;
import io.kubernetes.client.openapi.models.V1Deployment;
import io.kubernetes.client.openapi.models.V1DeploymentBuilder;
import io.kubernetes.client.openapi.models.V1DeploymentList;
import io.kubernetes.client.openapi.models.V1Endpoints;
import io.kubernetes.client.openapi.models.V1EndpointsList;
import io.kubernetes.client.openapi.models.V1EnvVar;
import io.kubernetes.client.openapi.models.V1Ingress;
import io.kubernetes.client.openapi.models.V1LoadBalancerIngress;
import io.kubernetes.client.openapi.models.V1LoadBalancerStatus;
import io.kubernetes.client.openapi.models.V1ReplicationController;
import io.kubernetes.client.openapi.models.V1ReplicationControllerList;
import io.kubernetes.client.openapi.models.V1Role;
import io.kubernetes.client.openapi.models.V1RoleBinding;
import io.kubernetes.client.openapi.models.V1Service;
import io.kubernetes.client.openapi.models.V1ServiceAccount;
import io.kubernetes.client.openapi.models.V1ServiceBuilder;
import io.kubernetes.client.util.Config;
import io.kubernetes.client.util.Yaml;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.testcontainers.k3s.K3sContainer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Ryan Baxter
*/
public class K8SUtils {
private static final String KUBERNETES_VERSION_FILE = "META-INF/springcloudkubernetes-version.txt";
private static final String WIREMOCK_DEPLOYMENT_NAME = "servicea-wiremock-deployment";
private static final String WIREMOCK_APP_NAME = "servicea-wiremock";
private final Log log = LogFactory.getLog(getClass());
private final CoreV1Api api;
private final AppsV1Api appsApi;
private final NetworkingV1Api networkingApi;
private final RbacAuthorizationV1Api rbacApi;
public static ApiClient createApiClient() throws IOException {
return createApiClient(false, Duration.ofSeconds(15));
}
public static String getPomVersion() {
try (InputStream in = new ClassPathResource(KUBERNETES_VERSION_FILE).getInputStream()) {
String version = StreamUtils.copyToString(in, StandardCharsets.UTF_8);
if (StringUtils.hasText(version)) {
version = version.trim();
}
return version;
}
catch (IOException e) {
ReflectionUtils.rethrowRuntimeException(e);
}
// not reachable since exception rethrown at runtime
return null;
}
public static ApiClient createApiClient(boolean debug, Duration readTimeout) throws IOException {
ApiClient client = Config.defaultClient();
client.setHttpClient(client.getHttpClient().newBuilder().readTimeout(readTimeout).build());
client.setDebugging(debug);
Configuration.setDefaultApiClient(client);
return client;
}
public static ApiClient createApiClient(String configFile) throws IOException {
ApiClient client = Config.fromConfig(new StringReader(configFile));
client.setHttpClient(client.getHttpClient().newBuilder().readTimeout(Duration.ofSeconds(15)).build());
client.setDebugging(false);
Configuration.setDefaultApiClient(client);
return client;
}
public K8SUtils(CoreV1Api api, AppsV1Api appsApi) {
this.api = api;
this.appsApi = appsApi;
this.networkingApi = new NetworkingV1Api();
this.rbacApi = new RbacAuthorizationV1Api();
}
public Object readYaml(String urlString) throws Exception {
// create the url
URL url = new URL(urlString);
BufferedReader reader = null;
Object yamlObj = null;
try {
// open the url stream, wrap it an a few "readers"
reader = new BufferedReader(new InputStreamReader(url.openStream()));
yamlObj = Yaml.load(reader);
}
catch (Exception e) {
throw e;
}
finally {
if (reader != null) {
reader.close();
}
}
return yamlObj;
}
public static Object readYamlFromClasspath(String fileName) throws Exception {
ClassLoader classLoader = K8SUtils.class.getClassLoader();
String file = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream(fileName))).lines()
.collect(Collectors.joining("\n"));
return Yaml.load(file);
}
public V1Service createService(String name, Map<String, String> labels, Map<String, String> specSelectors,
String type, String portName, int port, int targetPort, String namespace) throws ApiException {
V1Service wiremockService = new V1ServiceBuilder().editOrNewMetadata().withName(name).addToLabels(labels)
.endMetadata().editOrNewSpec().addToSelector(specSelectors).withType(type).addNewPort()
.withName(portName).withPort(port).withNewTargetPort(targetPort).endPort().endSpec().build();
return api.createNamespacedService(namespace, wiremockService, null, null, null, null);
}
public V1Deployment createDeployment(String name, Map<String, String> selectorMatchLabels,
Map<String, String> templateMetadataLabels, String containerName, String image, String pullPolicy,
int containerPort, int readinessProbePort, String readinessProbePath, int livenessProbePort,
String livenessProbePath, String serviceAccountName, Collection<V1EnvVar> envVars, String namespace)
throws ApiException {
V1Deployment wiremockDeployment = new V1DeploymentBuilder().editOrNewMetadata().withName(name).endMetadata()
.editOrNewSpec().withNewSelector().addToMatchLabels(selectorMatchLabels).endSelector()
.editOrNewTemplate().editOrNewMetadata().addToLabels(templateMetadataLabels).endMetadata()
.editOrNewSpec().withServiceAccountName(serviceAccountName).addNewContainer().withName(containerName)
.withImage(image).withImagePullPolicy(pullPolicy).addNewPort().withContainerPort(containerPort)
.endPort().editOrNewReadinessProbe().editOrNewHttpGet().withNewPort(readinessProbePort)
.withPath(readinessProbePath).endHttpGet().endReadinessProbe().editOrNewLivenessProbe()
.editOrNewHttpGet().withNewPort(livenessProbePort).withPath(livenessProbePath).endHttpGet()
.endLivenessProbe().addAllToEnv(envVars).endContainer().endSpec().endTemplate().endSpec().build();
return appsApi.createNamespacedDeployment(namespace, wiremockDeployment, null, null, null, null);
}
public void waitForEndpointReady(String name, String namespace) {
await().pollInterval(Duration.ofSeconds(1)).atMost(600, TimeUnit.SECONDS)
.until(() -> isEndpointReady(name, namespace));
}
public boolean isEndpointReady(String name, String namespace) throws ApiException {
V1EndpointsList endpoints = api.listNamespacedEndpoints(namespace, null, null, null, "metadata.name=" + name,
null, null, null, null, null, null);
if (endpoints.getItems().isEmpty()) {
fail("no endpoints for " + name);
}
V1Endpoints endpoint = endpoints.getItems().get(0);
return endpoint.getSubsets().get(0).getAddresses().size() >= 1;
}
public void waitForReplicationController(String name, String namespace) {
await().pollInterval(Duration.ofSeconds(1)).atMost(600, TimeUnit.SECONDS)
.until(() -> isReplicationControllerReady(name, namespace));
}
public boolean isReplicationControllerReady(String name, String namespace) throws ApiException {
V1ReplicationControllerList controllerList = api.listNamespacedReplicationController(namespace, null, null,
null, "metadata.name=" + name, null, null, null, null, null, null);
if (controllerList.getItems().size() < 1) {
fail("Replication controller with name " + name + "could not be found");
}
V1ReplicationController replicationController = controllerList.getItems().get(0);
Integer availableReplicas = replicationController.getStatus().getAvailableReplicas();
log.info("Available replicas for " + name + ": " + (availableReplicas == null ? 0 : availableReplicas));
return availableReplicas != null && availableReplicas >= 1;
}
public void waitForDeployment(String deploymentName, String namespace) {
await().pollInterval(Duration.ofSeconds(1)).atMost(600, TimeUnit.SECONDS)
.until(() -> isDeploymentReady(deploymentName, namespace));
}
public void waitForIngress(String ingressName, String namespace) {
await().timeout(Duration.ofSeconds(90)).pollInterval(Duration.ofSeconds(3)).until(() -> {
try {
V1LoadBalancerStatus status = networkingApi.readNamespacedIngress(ingressName, namespace, null)
.getStatus().getLoadBalancer();
if (status == null) {
log.info("ingress : " + ingressName + " not ready yet (loadbalancer not yet present)");
return false;
}
List<V1LoadBalancerIngress> loadBalancerIngress = status.getIngress();
if (loadBalancerIngress == null) {
log.info("ingress : " + ingressName + " not ready yet (loadbalancer ingress not yet present)");
return false;
}
String ip = loadBalancerIngress.get(0).getIp();
if (ip == null) {
log.info("ingress : " + ingressName + " not ready yet");
return false;
}
log.info("ingress : " + ingressName + " ready with ip : " + ip);
return true;
}
catch (ApiException e) {
if (e.getCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return false;
}
throw new RuntimeException(e);
}
});
}
public void waitForDeploymentToBeDeleted(String deploymentName, String namespace) {
await().timeout(Duration.ofSeconds(90)).until(() -> {
try {
appsApi.readNamespacedDeployment(deploymentName, namespace, null);
return false;
}
catch (ApiException e) {
if (e.getCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return true;
}
throw new RuntimeException(e);
}
});
}
public boolean isDeploymentReady(String deploymentName, String namespace) throws ApiException {
V1DeploymentList deployments = appsApi.listNamespacedDeployment(namespace, null, null, null,
"metadata.name=" + deploymentName, null, null, null, null, null, null);
if (deployments.getItems().size() < 1) {
fail("No deployments with the name " + deploymentName);
}
V1Deployment deployment = deployments.getItems().get(0);
Integer availableReplicas = deployment.getStatus().getAvailableReplicas();
log.info("Available replicas for " + deploymentName + ": "
+ (availableReplicas == null ? 0 : availableReplicas));
return availableReplicas != null && availableReplicas >= 1;
}
public void setUp(String namespace) throws Exception {
V1ServiceAccount serviceAccount = getConfigK8sClientItServiceAccount();
CheckedSupplier<V1ServiceAccount> accountSupplier = () -> api
.readNamespacedServiceAccount(serviceAccount.getMetadata().getName(), namespace, null);
CheckedSupplier<V1ServiceAccount> accountDefaulter = () -> api.createNamespacedServiceAccount(namespace,
serviceAccount, null, null, null, null);
notExistsHandler(accountSupplier, accountDefaulter);
V1RoleBinding roleBinding = getConfigK8sClientItRoleBinding();
notExistsHandler(() -> rbacApi.readNamespacedRoleBinding(roleBinding.getMetadata().getName(), namespace, null),
() -> rbacApi.createNamespacedRoleBinding(namespace, roleBinding, null, null, null, null));
V1Role role = getConfigK8sClientItRole();
notExistsHandler(() -> rbacApi.readNamespacedRole(role.getMetadata().getName(), namespace, null),
() -> rbacApi.createNamespacedRole(namespace, role, null, null, null, null));
}
public void deleteNamespace(String name) throws Exception {
api.deleteNamespace(name, null, null, null, null, null, null);
await().pollInterval(Duration.ofSeconds(1)).atMost(30, TimeUnit.SECONDS)
.until(() -> api.listNamespace(null, null, null, null, null, null, null, null, null, null).getItems()
.stream().noneMatch(x -> x.getMetadata().getName().equals(name)));
}
public void setUpClusterWide(String serviceAccountNamespace, Set<String> namespaces) throws Exception {
V1ServiceAccount serviceAccount = getConfigK8sClientItClusterServiceAccount();
CheckedSupplier<V1ServiceAccount> accountSupplier = () -> api
.readNamespacedServiceAccount(serviceAccount.getMetadata().getName(), serviceAccountNamespace, null);
CheckedSupplier<V1ServiceAccount> accountDefaulter = () -> api
.createNamespacedServiceAccount(serviceAccountNamespace, serviceAccount, null, null, null, null);
notExistsHandler(accountSupplier, accountDefaulter);
V1ClusterRole clusterRole = getConfigK8sClientItClusterRole();
notExistsHandler(() -> rbacApi.readClusterRole(clusterRole.getMetadata().getName(), null),
() -> rbacApi.createClusterRole(clusterRole, null, null, null, null));
V1RoleBinding roleBinding = getConfigK8sClientItClusterRoleBinding();
namespaces.forEach(namespace -> {
roleBinding.getMetadata().setNamespace(namespace);
try {
notExistsHandler(
() -> rbacApi.readNamespacedRoleBinding(roleBinding.getMetadata().getName(), namespace, null),
() -> rbacApi.createNamespacedRoleBinding(namespace, roleBinding, null, null, null, null));
}
catch (Exception e) {
throw new RuntimeException(e);
}
});
}
public static V1ServiceAccount getConfigK8sClientItClusterServiceAccount() throws Exception {
return (V1ServiceAccount) K8SUtils.readYamlFromClasspath("cluster/service-account.yaml");
}
public static V1ClusterRole getConfigK8sClientItClusterRole() throws Exception {
return (V1ClusterRole) K8SUtils.readYamlFromClasspath("cluster/cluster-role.yaml");
}
public static V1RoleBinding getConfigK8sClientItClusterRoleBinding() throws Exception {
return (V1RoleBinding) K8SUtils.readYamlFromClasspath("cluster/role-binding.yaml");
}
public static V1ServiceAccount getConfigK8sClientItServiceAccount() throws Exception {
return (V1ServiceAccount) K8SUtils.readYamlFromClasspath("setup/service-account.yaml");
}
public static V1RoleBinding getConfigK8sClientItRoleBinding() throws Exception {
return (V1RoleBinding) K8SUtils.readYamlFromClasspath("setup/role-binding.yaml");
}
public static V1Role getConfigK8sClientItRole() throws Exception {
return (V1Role) K8SUtils.readYamlFromClasspath("setup/role.yaml");
}
public void deployWiremock(String namespace, boolean rootPath, K3sContainer container) throws Exception {
innerDeployWiremock(namespace, rootPath, container);
// Check to make sure the wiremock deployment is ready
waitForDeployment(WIREMOCK_DEPLOYMENT_NAME, namespace);
// Check to see if endpoint is ready
waitForEndpointReady(WIREMOCK_APP_NAME, namespace);
}
/**
* this removes wiremock related manifests, but keeps the image loaded in the
* container. As such can be used across tests.
*/
public void cleanUpWiremock(String namespace) throws Exception {
appsApi.deleteCollectionNamespacedDeployment(namespace, null, null, null,
"metadata.name=" + WIREMOCK_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null, null);
api.deleteNamespacedService(WIREMOCK_APP_NAME, namespace, null, null, null, null, null, null);
networkingApi.deleteNamespacedIngress("wiremock-ingress", namespace, null, null, null, null, null, null);
waitForDeploymentToBeDeleted(WIREMOCK_DEPLOYMENT_NAME, namespace);
}
/**
* this one should be called once all tests in a suite are done, as it removes the
* image from a running container.
*/
public void removeWiremockImage() throws Exception {
V1Deployment wiremockDeployment = getWiremockDeployment();
String wiremockImage = getImageFromDeployment(wiremockDeployment);
Commons.cleanUpDownloadedImage(wiremockImage);
}
/**
* Gets the image from a Kubernetes Client deployment yaml. Assumes there is only one
* container defined in the deployment.
* @param deployment deployment yaml
* @return An array where the first item is the mage name and the second item is the
* tag
*/
public static String getImageFromDeployment(V1Deployment deployment) {
return deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage();
}
/**
* Gets the image from a Fabric8 deployment yaml. Assumes there is only one container
* defined in the deployment.
* @param deployment deployment yaml
* @return An array where the first item is the mage name and the second item is the
* tag
*/
public static String getImageFromDeployment(Deployment deployment) {
return deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage();
}
private void innerDeployWiremock(String namespace, boolean rootPath, K3sContainer container) throws Exception {
V1Deployment deployment = getWiremockDeployment();
String[] image = getImageFromDeployment(deployment).split(":", 2);
Commons.pullImage(image[0], image[1], container);
Commons.loadImage(image[0], image[1], "wiremock", container);
appsApi.createNamespacedDeployment(namespace, getWiremockDeployment(), null, null, null, null);
api.createNamespacedService(namespace, getWiremockAppService(), null, null, null, null);
V1Ingress ingress;
if (rootPath) {
ingress = getWiremockRootPathIngress();
}
else {
ingress = getWiremockIngress();
}
networkingApi.createNamespacedIngress(namespace, ingress, null, null, null, null);
waitForIngress(ingress.getMetadata().getName(), namespace);
}
private static V1Ingress getWiremockIngress() throws Exception {
return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock/wiremock-ingress.yaml");
}
private static V1Ingress getWiremockRootPathIngress() throws Exception {
return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock/wiremock-root-path-ingress.yaml");
}
private static V1Service getWiremockAppService() throws Exception {
return (V1Service) K8SUtils.readYamlFromClasspath("wiremock/wiremock-service.yaml");
}
private static V1Deployment getWiremockDeployment() throws Exception {
return (V1Deployment) K8SUtils.readYamlFromClasspath("wiremock/wiremock-deployment.yaml");
}
private static <T> void notExistsHandler(CheckedSupplier<T> callee, CheckedSupplier<T> defaulter) throws Exception {
try {
callee.get();
}
catch (Exception exception) {
if (exception instanceof ApiException apiException) {
if (apiException.getCode() == 404) {
defaulter.get();
return;
}
}
throw new RuntimeException(exception);
}
}
private interface CheckedSupplier<T> {
T get() throws Exception;
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.integration.tests.commons;
/**
* @author wind57
*/
public enum Phase {
/**
* Apply the manifests.
*/
CREATE,
/**
* Deleted the manifests.
*/
DELETE
}

View File

@@ -1,475 +0,0 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client;
import java.io.InputStream;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Secret;
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.apps.DeploymentList;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.api.model.networking.v1.IngressLoadBalancerIngress;
import io.fabric8.kubernetes.api.model.rbac.ClusterRole;
import io.fabric8.kubernetes.api.model.rbac.Role;
import io.fabric8.kubernetes.api.model.rbac.RoleBinding;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.dsl.base.PatchContext;
import io.fabric8.kubernetes.client.dsl.base.PatchType;
import jakarta.annotation.Nullable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.testcontainers.k3s.K3sContainer;
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.fail;
import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.loadImage;
import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.pomVersion;
import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.pullImage;
/**
* @author wind57
*/
public final class Util {
private static final Log LOG = LogFactory.getLog(Util.class);
private final K3sContainer container;
private final KubernetesClient client;
public Util(K3sContainer container) {
this.container = container;
this.client = new KubernetesClientBuilder().withConfig(Config.fromKubeconfig(container.getKubeConfigYaml()))
.build();
}
/**
* This is the preferred method to use when creating a deployment alongside with a
* service. It creates the given resources as-well as waits for them to be created.
* The delay check is intentionally not taken as an argument, so that it stays as
* tight as possible, providing reasonable defaults.
*
*/
public void createAndWait(String namespace, String name, @Nullable Deployment deployment, Service service,
@Nullable Ingress ingress, boolean changeVersion) {
try {
if (deployment != null) {
String imageFromDeployment = deployment.getSpec().getTemplate().getSpec().getContainers().get(0)
.getImage();
if (changeVersion) {
deployment.getSpec().getTemplate().getSpec().getContainers().get(0)
.setImage(imageFromDeployment + ":" + pomVersion());
}
else {
String[] image = imageFromDeployment.split(":", 2);
pullImage(image[0], image[1], container);
loadImage(image[0], image[1], name, container);
}
client.apps().deployments().inNamespace(namespace).resource(deployment).create();
waitForDeployment(namespace, deployment);
}
client.services().inNamespace(namespace).resource(service).create();
if (ingress != null) {
client.network().v1().ingresses().inNamespace(namespace).resource(ingress).create();
waitForIngress(namespace, ingress);
}
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public void busybox(String namespace, Phase phase) {
InputStream deploymentStream = inputStream("busybox/deployment.yaml");
InputStream serviceStream = inputStream("busybox/service.yaml");
Deployment deployment = client.apps().deployments().load(deploymentStream).item();
Service service = client.services().load(serviceStream).item();
if (phase.equals(Phase.CREATE)) {
createAndWait(namespace, "busybox", deployment, service, null, false);
}
else if (phase.equals(Phase.DELETE)) {
deleteAndWait(namespace, deployment, service, null);
}
}
public void deleteAndWait(String namespace, @Nullable Deployment deployment, Service service,
@Nullable Ingress ingress) {
try {
if (deployment != null) {
client.apps().deployments().inNamespace(namespace).resource(deployment).delete();
waitForDeploymentToBeDeleted(namespace, deployment);
}
client.services().inNamespace(namespace).resource(service).delete();
if (ingress != null) {
client.network().v1().ingresses().inNamespace(namespace).resource(ingress).delete();
waitForIngressToBeDeleted(namespace, ingress);
}
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public void setUp(String namespace) throws Exception {
InputStream serviceAccountAsStream = inputStream("setup/service-account.yaml");
InputStream roleBindingAsStream = inputStream("setup/role-binding.yaml");
InputStream roleAsStream = inputStream("setup/role.yaml");
innerSetup(namespace, serviceAccountAsStream, roleBindingAsStream, roleAsStream);
}
public InputStream inputStream(String fileName) {
return Util.class.getClassLoader().getResourceAsStream(fileName);
}
public void createNamespace(String name) {
try {
client.namespaces().resource(new NamespaceBuilder().withNewMetadata().withName(name).and().build())
.create();
await().pollInterval(Duration.ofSeconds(1)).atMost(30, TimeUnit.SECONDS).until(() -> client.namespaces()
.list().getItems().stream().anyMatch(x -> x.getMetadata().getName().equals(name)));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public void deleteNamespace(String name) {
try {
client.namespaces().resource(new NamespaceBuilder().withNewMetadata().withName(name).and().build())
.delete();
await().pollInterval(Duration.ofSeconds(1)).atMost(30, TimeUnit.SECONDS).until(() -> client.namespaces()
.list().getItems().stream().noneMatch(x -> x.getMetadata().getName().equals(name)));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public void setUpClusterWide(String serviceAccountNamespace, Set<String> namespaces) {
InputStream clusterRoleBindingAsStream = inputStream("cluster/cluster-role.yaml");
InputStream serviceAccountAsStream = inputStream("cluster/service-account.yaml");
InputStream roleBindingAsStream = inputStream("cluster/role-binding.yaml");
ClusterRole clusterRole = client.rbac().clusterRoles().load(clusterRoleBindingAsStream).item();
if (client.rbac().clusterRoles().withName(clusterRole.getMetadata().getName()).get() == null) {
client.rbac().clusterRoles().resource(clusterRole).create();
}
ServiceAccount serviceAccountFromStream = client.serviceAccounts().load(serviceAccountAsStream).item();
serviceAccountFromStream.getMetadata().setNamespace(serviceAccountNamespace);
if (client.serviceAccounts().inNamespace(serviceAccountNamespace)
.withName(serviceAccountFromStream.getMetadata().getName()).get() == null) {
client.serviceAccounts().inNamespace(serviceAccountNamespace).resource(serviceAccountFromStream).create();
}
RoleBinding roleBindingFromStream = client.rbac().roleBindings().load(roleBindingAsStream).item();
namespaces.forEach(namespace -> {
roleBindingFromStream.getMetadata().setNamespace(namespace);
if (client.rbac().roleBindings().inNamespace(namespace)
.withName(roleBindingFromStream.getMetadata().getName()).get() == null) {
client.rbac().roleBindings().inNamespace(namespace).resource(roleBindingFromStream).create();
}
});
}
public void createAndWait(String namespace, @Nullable ConfigMap configMap, @Nullable Secret secret) {
if (configMap != null) {
client.configMaps().resource(configMap).create();
waitForConfigMap(namespace, configMap, Phase.CREATE);
}
if (secret != null) {
client.secrets().resource(secret).create();
waitForSecret(namespace, secret, Phase.CREATE);
}
}
public void deleteAndWait(String namespace, @Nullable ConfigMap configMap, @Nullable Secret secret) {
if (configMap != null) {
client.configMaps().resource(configMap).delete();
waitForConfigMap(namespace, configMap, Phase.DELETE);
}
if (secret != null) {
client.secrets().resource(secret).delete();
waitForSecret(namespace, secret, Phase.DELETE);
}
}
public void setUpIstio(String namespace) {
InputStream serviceAccountAsStream = inputStream("istio/service-account.yaml");
InputStream roleBindingAsStream = inputStream("istio/role-binding.yaml");
InputStream roleAsStream = inputStream("istio/role.yaml");
innerSetup(namespace, serviceAccountAsStream, roleBindingAsStream, roleAsStream);
}
private void waitForConfigMap(String namespace, ConfigMap configMap, Phase phase) {
String configMapName = configMapName(configMap);
await().pollInterval(Duration.ofSeconds(1)).atMost(600, TimeUnit.SECONDS).until(() -> {
int size = (int) client.configMaps().inNamespace(namespace).list().getItems().stream()
.filter(x -> x.getMetadata().getName().equals(configMapName)).count();
if (size == 0) {
return !phase.equals(Phase.CREATE);
}
return phase.equals(Phase.CREATE);
});
}
public void wiremock(String namespace, String path, Phase phase) {
wiremock(namespace, path, phase, true);
}
public void wiremock(String namespace, String path, Phase phase, boolean withIngress) {
InputStream deploymentStream = inputStream("wiremock/wiremock-deployment.yaml");
InputStream serviceStream = inputStream("wiremock/wiremock-service.yaml");
InputStream ingressStream = inputStream("wiremock/wiremock-ingress.yaml");
Deployment deployment = client.apps().deployments().load(deploymentStream).item();
Service service = client.services().load(serviceStream).item();
Ingress ingress = null;
if (phase.equals(Phase.CREATE)) {
if (withIngress) {
ingress = client.network().v1().ingresses().load(ingressStream).get();
ingress.getMetadata().setNamespace(namespace);
ingress.getSpec().getRules().get(0).getHttp().getPaths().get(0).setPath(path);
}
deployment.getMetadata().setNamespace(namespace);
service.getMetadata().setNamespace(namespace);
createAndWait(namespace, "wiremock", deployment, service, ingress, false);
}
else {
if (withIngress) {
ingress = client.network().v1().ingresses().load(ingressStream).get();
}
deleteAndWait(namespace, deployment, service, ingress);
}
}
private void waitForSecret(String namespace, Secret secret, Phase phase) {
String secretName = secretName(secret);
await().pollInterval(Duration.ofSeconds(1)).atMost(600, TimeUnit.SECONDS).until(() -> {
int size = (int) client.secrets().inNamespace(namespace).list().getItems().stream()
.filter(x -> x.getMetadata().getName().equals(secretName)).count();
if (size == 0) {
return !phase.equals(Phase.CREATE);
}
return phase.equals(Phase.CREATE);
});
}
private void waitForIngressToBeDeleted(String namespace, Ingress ingress) {
String ingressName = ingressName(ingress);
await().pollInterval(Duration.ofSeconds(1)).atMost(30, TimeUnit.SECONDS).until(() -> {
Ingress inner = client.network().v1().ingresses().inNamespace(namespace).withName(ingressName).get();
return inner == null;
});
}
private void waitForDeploymentToBeDeleted(String namespace, Deployment deployment) {
String deploymentName = deploymentName(deployment);
Map<String, String> matchLabels = deployment.getSpec().getSelector().getMatchLabels();
await().pollInterval(Duration.ofSeconds(1)).atMost(30, TimeUnit.SECONDS).until(() -> {
Deployment inner = client.apps().deployments().inNamespace(namespace).withName(deploymentName).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();
});
}
private void waitForDeployment(String namespace, Deployment deployment) {
String deploymentName = deploymentName(deployment);
await().pollInterval(Duration.ofSeconds(2)).atMost(600, TimeUnit.SECONDS)
.until(() -> isDeploymentReady(namespace, deploymentName));
}
private boolean isDeploymentReady(String namespace, String deploymentName) {
Deployment deployment = client.apps().deployments().inNamespace(namespace).withName(deploymentName).get();
Integer availableReplicas = deployment.getStatus().getAvailableReplicas();
LOG.info("Available replicas for " + deploymentName + ": " + ((availableReplicas == null) ? 0 : 1));
return availableReplicas != null && availableReplicas >= 1;
}
public void waitForIngress(String namespace, Ingress ingress) {
String ingressName = ingressName(ingress);
try {
await().pollInterval(Duration.ofSeconds(2)).atMost(180, TimeUnit.SECONDS).until(() -> {
Ingress inner = client.network().v1().ingresses().inNamespace(namespace).withName(ingressName).get();
if (inner == null) {
LOG.info("ingress : " + ingressName + " not ready yet present");
return false;
}
List<IngressLoadBalancerIngress> loadBalancerIngress = inner.getStatus().getLoadBalancer().getIngress();
if (loadBalancerIngress == null || loadBalancerIngress.isEmpty()) {
LOG.info("ingress : " + ingressName + " not ready yet (loadbalancer ingress not yet present)");
return false;
}
String ip = loadBalancerIngress.get(0).getIp();
if (ip == null) {
LOG.info("ingress : " + ingressName + " not ready yet");
return false;
}
LOG.info("ingress : " + ingressName + " ready with ip : " + ip);
return true;
});
}
catch (Exception e) {
LOG.error("Error waiting for ingress");
e.printStackTrace();
}
}
public void patchWithReplace(String imageName, String deploymentName, String namespace, String patchBody,
Map<String, String> labels) {
String body = patchBody.replace("image_name_here", imageName);
client.apps().deployments().inNamespace(namespace).withName(deploymentName)
.patch(PatchContext.of(PatchType.JSON_MERGE), body);
waitForDeploymentAfterPatch(deploymentName, namespace, labels);
}
private void waitForDeploymentAfterPatch(String deploymentName, String namespace, Map<String, String> labels) {
try {
await().pollDelay(Duration.ofSeconds(4)).pollInterval(Duration.ofSeconds(3)).atMost(60, TimeUnit.SECONDS)
.until(() -> isDeploymentReadyAfterPatch(deploymentName, namespace, labels));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private boolean isDeploymentReadyAfterPatch(String deploymentName, String namespace, Map<String, String> labels) {
DeploymentList deployments = client.apps().deployments().inNamespace(namespace).list();
if (deployments.getItems().isEmpty()) {
fail("No deployment with name " + deploymentName);
}
Deployment deployment = deployments.getItems().get(0);
// if no replicas are defined, it means only 1 is needed
int replicas = Optional.ofNullable(deployment.getSpec().getReplicas()).orElse(1);
int numberOfPods = client.pods().inNamespace(namespace).withLabels(labels).list().getItems().size();
if (numberOfPods != replicas) {
LOG.info("number of pods not yet stabilized");
return false;
}
return replicas == Optional.ofNullable(deployment.getStatus().getReadyReplicas()).orElse(0);
}
private void innerSetup(String namespace, InputStream serviceAccountAsStream, InputStream roleBindingAsStream,
InputStream roleAsStream) {
ServiceAccount serviceAccountFromStream = client.serviceAccounts().inNamespace(namespace)
.load(serviceAccountAsStream).item();
if (client.serviceAccounts().inNamespace(namespace).withName(serviceAccountFromStream.getMetadata().getName())
.get() == null) {
client.serviceAccounts().inNamespace(namespace).resource(serviceAccountFromStream).create();
}
RoleBinding roleBindingFromStream = client.rbac().roleBindings().inNamespace(namespace)
.load(roleBindingAsStream).item();
if (client.rbac().roleBindings().inNamespace(namespace).withName(roleBindingFromStream.getMetadata().getName())
.get() == null) {
client.rbac().roleBindings().inNamespace(namespace).resource(roleBindingFromStream).create();
}
Role roleFromStream = client.rbac().roles().inNamespace(namespace).load(roleAsStream).item();
if (client.rbac().roles().inNamespace(namespace).withName(roleFromStream.getMetadata().getName())
.get() == null) {
client.rbac().roles().inNamespace(namespace).resource(roleFromStream).create();
}
}
private String deploymentName(Deployment deployment) {
return deployment.getMetadata().getName();
}
private String ingressName(Ingress ingress) {
return ingress.getMetadata().getName();
}
private String configMapName(ConfigMap configMap) {
return configMap.getMetadata().getName();
}
private String secretName(Secret secret) {
return secret.getMetadata().getName();
}
public KubernetesClient client() {
return client;
}
}

View File

@@ -1,717 +0,0 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.integration.tests.commons.native_client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.apis.AppsV1Api;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
import io.kubernetes.client.openapi.apis.RbacAuthorizationV1Api;
import io.kubernetes.client.openapi.models.V1ClusterRole;
import io.kubernetes.client.openapi.models.V1ClusterRoleBinding;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1Deployment;
import io.kubernetes.client.openapi.models.V1DeploymentList;
import io.kubernetes.client.openapi.models.V1Ingress;
import io.kubernetes.client.openapi.models.V1LoadBalancerIngress;
import io.kubernetes.client.openapi.models.V1LoadBalancerStatus;
import io.kubernetes.client.openapi.models.V1NamespaceBuilder;
import io.kubernetes.client.openapi.models.V1Role;
import io.kubernetes.client.openapi.models.V1RoleBinding;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.openapi.models.V1Service;
import io.kubernetes.client.openapi.models.V1ServiceAccount;
import io.kubernetes.client.util.Config;
import io.kubernetes.client.util.PatchUtils;
import io.kubernetes.client.util.Yaml;
import jakarta.annotation.Nullable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.testcontainers.k3s.K3sContainer;
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.fail;
import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.loadImage;
import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.pomVersion;
import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.pullImage;
/**
* @author wind57
*/
public final class Util {
private static final Log LOG = LogFactory.getLog(Util.class);
private final CoreV1Api coreV1Api;
private final AppsV1Api appsV1Api;
private final NetworkingV1Api networkingV1Api;
private final RbacAuthorizationV1Api rbacApi;
private final K3sContainer container;
public Util(K3sContainer container) {
ApiClient client;
try {
client = Config.fromConfig(new StringReader(container.getKubeConfigYaml()));
}
catch (IOException e) {
throw new RuntimeException(e);
}
client.setHttpClient(client.getHttpClient().newBuilder().readTimeout(Duration.ofSeconds(15)).build());
client.setDebugging(false);
Configuration.setDefaultApiClient(client);
this.container = container;
this.coreV1Api = new CoreV1Api();
this.appsV1Api = new AppsV1Api();
this.networkingV1Api = new NetworkingV1Api();
rbacApi = new RbacAuthorizationV1Api();
}
/**
* This is the preferred method to use when creating a deployment alongside with a
* service. It creates the given resources as-well as waits for them to be created.
* The delay check is intentionally not taken as an argument, so that it stays as
* tight as possible, providing reasonable defaults.
*
*/
public void createAndWait(String namespace, String name, V1Deployment deployment, V1Service service,
@Nullable V1Ingress ingress, boolean changeVersion) {
try {
String imageFromDeployment = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage();
if (changeVersion) {
deployment.getSpec().getTemplate().getSpec().getContainers().get(0)
.setImage(imageFromDeployment + ":" + pomVersion());
}
else {
String[] image = imageFromDeployment.split(":", 2);
pullImage(image[0], image[1], container);
loadImage(image[0], image[1], name, container);
}
appsV1Api.createNamespacedDeployment(namespace, deployment, null, null, null, null);
coreV1Api.createNamespacedService(namespace, service, null, null, null, null);
waitForDeployment(namespace, deployment);
if (ingress != null) {
networkingV1Api.createNamespacedIngress(namespace, ingress, null, null, null, null);
waitForIngress(namespace, ingress);
}
}
catch (Exception e) {
if (e instanceof ApiException apiException) {
System.out.println(apiException.getResponseBody());
}
throw new RuntimeException(e);
}
}
public void createAndWait(String namespace, @Nullable V1ConfigMap configMap, @Nullable V1Secret secret) {
try {
if (configMap != null) {
coreV1Api.createNamespacedConfigMap(namespace, configMap, null, null, null, null);
waitForConfigMap(namespace, configMap, Phase.CREATE);
}
if (secret != null) {
coreV1Api.createNamespacedSecret(namespace, secret, null, null, null, null);
waitForSecret(namespace, secret, Phase.CREATE);
}
}
catch (ApiException e) {
throw new RuntimeException(e);
}
}
public void deleteAndWait(String namespace, @Nullable V1ConfigMap configMap, @Nullable V1Secret secret) {
try {
if (configMap != null) {
String configMapName = configMapName(configMap);
coreV1Api.deleteNamespacedConfigMap(configMapName, namespace, null, null, null, null, null, null);
waitForConfigMap(namespace, configMap, Phase.DELETE);
}
if (secret != null) {
String secretName = secretName(secret);
coreV1Api.deleteNamespacedSecret(secretName, namespace, null, null, null, null, null, null);
waitForSecret(namespace, secret, Phase.DELETE);
}
}
catch (ApiException e) {
throw new RuntimeException(e);
}
}
public void createNamespace(String name) {
try {
coreV1Api.createNamespace(new V1NamespaceBuilder().withNewMetadata().withName(name).and().build(), null,
null, null, null);
}
catch (ApiException e) {
throw new RuntimeException(e);
}
}
public void deleteAndWait(String namespace, V1Deployment deployment, V1Service service,
@Nullable V1Ingress ingress) {
String deploymentName = deploymentName(deployment);
String serviceName = serviceName(service);
try {
Map<String, String> podLabels = appsV1Api.readNamespacedDeployment(deploymentName, namespace, null)
.getSpec().getTemplate().getMetadata().getLabels();
appsV1Api.deleteNamespacedDeployment(deploymentName, namespace, null, null, null, null, null, null);
coreV1Api.deleteNamespacedService(serviceName, namespace, null, null, null, null, null, null);
coreV1Api.deleteCollectionNamespacedPod(namespace, null, null, null, null, null, labelSelector(podLabels),
null, null, null, null, null, null, null);
waitForDeploymentToBeDeleted(deploymentName, namespace);
waitForDeploymentPodsToBeDeleted(podLabels, namespace);
if (ingress != null) {
String ingressName = ingressName(ingress);
networkingV1Api.deleteNamespacedIngress(ingressName, namespace, null, null, null, null, null, null);
waitForIngressToBeDeleted(ingressName, namespace);
}
}
catch (ApiException e) {
throw new RuntimeException(e);
}
}
public void busybox(String namespace, Phase phase) {
V1Deployment deployment = (V1Deployment) yaml("busybox/deployment.yaml");
V1Service service = (V1Service) yaml("busybox/service.yaml");
if (phase.equals(Phase.CREATE)) {
createAndWait(namespace, "busybox", deployment, service, null, false);
}
else if (phase.equals(Phase.DELETE)) {
deleteAndWait(namespace, deployment, service, null);
}
}
public void kafka(String namespace, Phase phase) {
V1Deployment deployment = (V1Deployment) yaml("kafka/kafka-deployment.yaml");
V1Service service = (V1Service) yaml("kafka/kafka-service.yaml");
if (phase.equals(Phase.CREATE)) {
createAndWait(namespace, "kafka", deployment, service, null, false);
}
else if (phase.equals(Phase.DELETE)) {
deleteAndWait(namespace, deployment, service, null);
}
}
public void rabbitMq(String namespace, Phase phase) {
V1Deployment deployment = (V1Deployment) yaml("rabbitmq/rabbitmq-deployment.yaml");
V1Service service = (V1Service) yaml("rabbitmq/rabbitmq-service.yaml");
if (phase.equals(Phase.CREATE)) {
createAndWait(namespace, "rabbitmq", deployment, service, null, false);
}
else if (phase.equals(Phase.DELETE)) {
deleteAndWait(namespace, deployment, service, null);
}
}
public void zookeeper(String namespace, Phase phase) {
V1Deployment deployment = (V1Deployment) yaml("zookeeper/zookeeper-deployment.yaml");
V1Service service = (V1Service) yaml("zookeeper/zookeeper-service.yaml");
if (phase.equals(Phase.CREATE)) {
createAndWait(namespace, "zookeeper", deployment, service, null, false);
}
else if (phase.equals(Phase.DELETE)) {
deleteAndWait(namespace, deployment, service, null);
}
}
/**
* reads a yaml from classpath, fails if not found.
*/
public Object yaml(String fileName) {
ClassLoader classLoader = Util.class.getClassLoader();
String file = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream(fileName))).lines()
.collect(Collectors.joining("\n"));
try {
return Yaml.load(file);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
public void setUp(String namespace) {
try {
V1ServiceAccount serviceAccount = (V1ServiceAccount) yaml("setup/service-account.yaml");
CheckedSupplier<V1ServiceAccount> accountSupplier = () -> coreV1Api
.readNamespacedServiceAccount(serviceAccount.getMetadata().getName(), namespace, null);
CheckedSupplier<V1ServiceAccount> accountDefaulter = () -> coreV1Api
.createNamespacedServiceAccount(namespace, serviceAccount, null, null, null, null);
notExistsHandler(accountSupplier, accountDefaulter);
V1RoleBinding roleBinding = (V1RoleBinding) yaml("setup/role-binding.yaml");
notExistsHandler(
() -> rbacApi.readNamespacedRoleBinding(roleBinding.getMetadata().getName(), namespace, null),
() -> rbacApi.createNamespacedRoleBinding(namespace, roleBinding, null, null, null, null));
V1Role role = (V1Role) yaml("setup/role.yaml");
notExistsHandler(() -> rbacApi.readNamespacedRole(role.getMetadata().getName(), namespace, null),
() -> rbacApi.createNamespacedRole(namespace, role, null, null, null, null));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public void setUpClusterWideClusterRoleBinding(String serviceAccountNamespace) {
try {
V1ServiceAccount serviceAccount = (V1ServiceAccount) yaml("cluster/service-account.yaml");
CheckedSupplier<V1ServiceAccount> accountSupplier = () -> coreV1Api.readNamespacedServiceAccount(
serviceAccount.getMetadata().getName(), serviceAccountNamespace, null);
CheckedSupplier<V1ServiceAccount> accountDefaulter = () -> coreV1Api
.createNamespacedServiceAccount(serviceAccountNamespace, serviceAccount, null, null, null, null);
notExistsHandler(accountSupplier, accountDefaulter);
V1ClusterRole clusterRole = (V1ClusterRole) yaml("cluster/cluster-role.yaml");
notExistsHandler(() -> rbacApi.readClusterRole(clusterRole.getMetadata().getName(), null),
() -> rbacApi.createClusterRole(clusterRole, null, null, null, null));
V1ClusterRoleBinding clusterRoleBinding = (V1ClusterRoleBinding) yaml("cluster/cluster-role-binding.yaml");
notExistsHandler(() -> rbacApi.readClusterRoleBinding(clusterRoleBinding.getMetadata().getName(), null),
() -> rbacApi.createClusterRoleBinding(clusterRoleBinding, null, null, null, null));
}
catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public void deleteClusterWideClusterRoleBinding(String serviceAccountNamespace) {
try {
V1ServiceAccount serviceAccount = (V1ServiceAccount) yaml("cluster/service-account.yaml");
V1ClusterRole clusterRole = (V1ClusterRole) yaml("cluster/cluster-role.yaml");
V1ClusterRoleBinding clusterRoleBinding = (V1ClusterRoleBinding) yaml("cluster/cluster-role-binding.yaml");
coreV1Api.deleteNamespacedServiceAccount(serviceAccount.getMetadata().getName(), serviceAccountNamespace,
null, null, null, null, null, null);
rbacApi.deleteClusterRole(clusterRole.getMetadata().getName(), null, null, null, null, null, null);
rbacApi.deleteClusterRoleBinding(clusterRoleBinding.getMetadata().getName(), null, null, null, null, null,
null);
}
catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public void setUpClusterWide(String serviceAccountNamespace, Set<String> namespaces) {
try {
V1ServiceAccount serviceAccount = (V1ServiceAccount) yaml("cluster/service-account.yaml");
CheckedSupplier<V1ServiceAccount> accountSupplier = () -> coreV1Api.readNamespacedServiceAccount(
serviceAccount.getMetadata().getName(), serviceAccountNamespace, null);
CheckedSupplier<V1ServiceAccount> accountDefaulter = () -> coreV1Api
.createNamespacedServiceAccount(serviceAccountNamespace, serviceAccount, null, null, null, null);
notExistsHandler(accountSupplier, accountDefaulter);
V1ClusterRole clusterRole = (V1ClusterRole) yaml("cluster/cluster-role.yaml");
notExistsHandler(() -> rbacApi.readClusterRole(clusterRole.getMetadata().getName(), null),
() -> rbacApi.createClusterRole(clusterRole, null, null, null, null));
V1RoleBinding roleBinding = (V1RoleBinding) yaml("cluster/role-binding.yaml");
namespaces.forEach(namespace -> {
roleBinding.getMetadata().setNamespace(namespace);
try {
notExistsHandler(
() -> rbacApi.readNamespacedRoleBinding(roleBinding.getMetadata().getName(), namespace,
null),
() -> rbacApi.createNamespacedRoleBinding(namespace, roleBinding, null, null, null, null));
}
catch (Exception e) {
throw new RuntimeException(e);
}
});
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public void deleteClusterWide(String serviceAccountNamespace, Set<String> namespaces) {
try {
V1ServiceAccount serviceAccount = (V1ServiceAccount) yaml("cluster/service-account.yaml");
V1ClusterRole clusterRole = (V1ClusterRole) yaml("cluster/cluster-role.yaml");
V1RoleBinding roleBinding = (V1RoleBinding) yaml("cluster/role-binding.yaml");
coreV1Api.deleteNamespacedServiceAccount(serviceAccount.getMetadata().getName(), serviceAccountNamespace,
null, null, null, null, null, null);
rbacApi.deleteClusterRole(clusterRole.getMetadata().getName(), null, null, null, null, null, null);
namespaces.forEach(namespace -> {
roleBinding.getMetadata().setNamespace(namespace);
try {
rbacApi.deleteNamespacedRoleBinding(roleBinding.getMetadata().getName(), namespace, null, null,
null, null, null, null);
}
catch (Exception e) {
throw new RuntimeException(e);
}
});
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public void deleteNamespace(String name) {
try {
coreV1Api.deleteNamespace(name, null, null, null, null, null, null);
}
catch (ApiException e) {
throw new RuntimeException(e);
}
await().pollInterval(Duration.ofSeconds(1)).atMost(30, TimeUnit.SECONDS)
.until(() -> coreV1Api.listNamespace(null, null, null, null, null, null, null, null, null, null)
.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);
}
public void wiremock(String namespace, String path, Phase phase, boolean withIngress) {
V1Deployment deployment = (V1Deployment) yaml("wiremock/wiremock-deployment.yaml");
V1Service service = (V1Service) yaml("wiremock/wiremock-service.yaml");
V1Ingress ingress = null;
if (phase.equals(Phase.CREATE)) {
if (withIngress) {
ingress = (V1Ingress) yaml("wiremock/wiremock-ingress.yaml");
ingress.getMetadata().setNamespace(namespace);
ingress.getSpec().getRules().get(0).getHttp().getPaths().get(0).setPath(path);
}
deployment.getMetadata().setNamespace(namespace);
service.getMetadata().setNamespace(namespace);
createAndWait(namespace, "wiremock", deployment, service, ingress, false);
}
else {
if (withIngress) {
ingress = (V1Ingress) yaml("wiremock/wiremock-ingress.yaml");
}
deleteAndWait(namespace, deployment, service, ingress);
}
}
public static void patchWithMerge(String deploymentName, String namespace, String patchBody,
Map<String, String> podLabels) {
try {
PatchUtils.patch(V1Deployment.class,
() -> new AppsV1Api().patchNamespacedDeploymentCall(deploymentName, namespace,
new V1Patch(patchBody), null, null, null, null, null, null),
V1Patch.PATCH_FORMAT_STRATEGIC_MERGE_PATCH, new CoreV1Api().getApiClient());
}
catch (ApiException e) {
LOG.error("error : " + e.getResponseBody());
throw new RuntimeException(e);
}
waitForDeploymentAfterPatch(deploymentName, namespace, podLabels);
}
public static void patchWithReplace(String imageName, String deploymentName, String namespace, String patchBody,
Map<String, String> podLabels) {
String body = patchBody.replace("image_name_here", imageName);
try {
PatchUtils.patch(V1Deployment.class,
() -> new AppsV1Api().patchNamespacedDeploymentCall(deploymentName, namespace, new V1Patch(body),
null, null, null, null, null, null),
V1Patch.PATCH_FORMAT_JSON_MERGE_PATCH, new CoreV1Api().getApiClient());
}
catch (ApiException e) {
LOG.error("error : " + e.getResponseBody());
throw new RuntimeException(e);
}
waitForDeploymentAfterPatch(deploymentName, namespace, podLabels);
}
private String deploymentName(V1Deployment deployment) {
return deployment.getMetadata().getName();
}
private String serviceName(V1Service service) {
return service.getMetadata().getName();
}
private String ingressName(V1Ingress ingress) {
return ingress.getMetadata().getName();
}
private String configMapName(V1ConfigMap configMap) {
return configMap.getMetadata().getName();
}
private String secretName(V1Secret secret) {
return secret.getMetadata().getName();
}
private void waitForDeployment(String namespace, V1Deployment deployment) {
String deploymentName = deploymentName(deployment);
await().pollDelay(Duration.ofSeconds(5)).pollInterval(Duration.ofSeconds(5)).atMost(900, TimeUnit.SECONDS)
.until(() -> isDeploymentReady(deploymentName, namespace));
}
private void waitForConfigMap(String namespace, V1ConfigMap configMap, Phase phase) {
String configMapName = configMapName(configMap);
await().pollInterval(Duration.ofSeconds(1)).atMost(600, TimeUnit.SECONDS).until(() -> {
try {
coreV1Api.readNamespacedConfigMap(configMapName, namespace, null);
return phase.equals(Phase.CREATE);
}
catch (ApiException e) {
if (e.getCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return !phase.equals(Phase.CREATE);
}
throw new RuntimeException(e);
}
});
}
private void waitForSecret(String namespace, V1Secret secret, Phase phase) {
String secretName = secretName(secret);
await().pollInterval(Duration.ofSeconds(1)).atMost(600, TimeUnit.SECONDS).until(() -> {
try {
coreV1Api.readNamespacedSecret(secretName, namespace, null);
return phase.equals(Phase.CREATE);
}
catch (ApiException e) {
if (e.getCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return !phase.equals(Phase.CREATE);
}
throw new RuntimeException(e);
}
});
}
private void waitForIngress(String namespace, V1Ingress ingress) {
String ingressName = ingressName(ingress);
await().timeout(Duration.ofSeconds(90)).pollInterval(Duration.ofSeconds(3)).until(() -> {
try {
V1LoadBalancerStatus status = networkingV1Api.readNamespacedIngress(ingressName, namespace, null)
.getStatus().getLoadBalancer();
if (status == null) {
LOG.info("ingress : " + ingressName + " not ready yet (loadbalancer not yet present)");
return false;
}
List<V1LoadBalancerIngress> loadBalancerIngress = status.getIngress();
if (loadBalancerIngress == null) {
LOG.info("ingress : " + ingressName + " not ready yet (loadbalancer ingress not yet present)");
return false;
}
String ip = loadBalancerIngress.get(0).getIp();
if (ip == null) {
LOG.info("ingress : " + ingressName + " not ready yet");
return false;
}
LOG.info("ingress : " + ingressName + " ready with ip : " + ip);
return true;
}
catch (ApiException e) {
if (e.getCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return false;
}
throw new RuntimeException(e);
}
});
}
private void waitForDeploymentToBeDeleted(String deploymentName, String namespace) {
await().timeout(Duration.ofSeconds(180)).until(() -> {
try {
appsV1Api.readNamespacedDeployment(deploymentName, namespace, null);
return false;
}
catch (ApiException e) {
if (e.getCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return true;
}
throw new RuntimeException(e);
}
});
}
private void waitForDeploymentPodsToBeDeleted(Map<String, String> labels, String namespace) {
await().timeout(Duration.ofSeconds(180)).until(() -> {
try {
int currentNumberOfPods = coreV1Api.listNamespacedPod(namespace, null, null, null, null,
labelSelector(labels), null, null, null, null, null).getItems().size();
return currentNumberOfPods == 0;
}
catch (ApiException e) {
if (e.getCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return true;
}
throw new RuntimeException(e);
}
});
}
private void waitForIngressToBeDeleted(String ingressName, String namespace) {
await().timeout(Duration.ofSeconds(90)).until(() -> {
try {
networkingV1Api.readNamespacedIngress(ingressName, namespace, null);
return false;
}
catch (ApiException e) {
if (e.getCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return true;
}
throw new RuntimeException(e);
}
});
}
private boolean isDeploymentReady(String deploymentName, String namespace) throws ApiException {
V1DeploymentList deployments = appsV1Api.listNamespacedDeployment(namespace, null, null, null,
"metadata.name=" + deploymentName, null, null, null, null, null, null);
if (deployments.getItems().isEmpty()) {
fail("No deployments with the name " + deploymentName);
}
V1Deployment deployment = deployments.getItems().get(0);
Integer availableReplicas = deployment.getStatus().getAvailableReplicas();
LOG.info("Available replicas for " + deploymentName + ": "
+ (availableReplicas == null ? 0 : availableReplicas));
return availableReplicas != null && availableReplicas >= 1;
}
private static void waitForDeploymentAfterPatch(String deploymentName, String namespace,
Map<String, String> podLabels) {
try {
await().pollDelay(Duration.ofSeconds(4)).pollInterval(Duration.ofSeconds(3)).atMost(60, TimeUnit.SECONDS)
.until(() -> isDeploymentReadyAfterPatch(deploymentName, namespace, podLabels));
}
catch (Exception e) {
if (e instanceof ApiException apiException) {
LOG.error("Error: ");
LOG.error(apiException.getResponseBody());
}
throw new RuntimeException(e);
}
}
private static boolean isDeploymentReadyAfterPatch(String deploymentName, String namespace,
Map<String, String> podLabels) throws ApiException {
V1DeploymentList deployments = new AppsV1Api().listNamespacedDeployment(namespace, null, null, null,
"metadata.name=" + deploymentName, null, null, null, null, null, null);
if (deployments.getItems().isEmpty()) {
fail("No deployment with name " + deploymentName);
}
V1Deployment deployment = deployments.getItems().get(0);
// if no replicas are defined, it means only 1 is needed
int replicas = Optional.ofNullable(deployment.getSpec().getReplicas()).orElse(1);
int readyReplicas = Optional.ofNullable(deployment.getStatus().getReadyReplicas()).orElse(0);
if (readyReplicas != replicas) {
LOG.info("ready replicas not yet same as replicas");
return false;
}
int pods = new CoreV1Api().listNamespacedPod(namespace, null, null, null, null, labelSelector(podLabels), null,
null, null, null, null).getItems().size();
if (pods != replicas) {
LOG.info("number of pods not yet stabilized");
return false;
}
return true;
}
private static <T> void notExistsHandler(CheckedSupplier<T> callee, CheckedSupplier<T> defaulter) throws Exception {
try {
callee.get();
}
catch (Exception exception) {
if (exception instanceof ApiException apiException) {
if (apiException.getCode() == 404) {
defaulter.get();
return;
}
}
throw new RuntimeException(exception);
}
}
private static String labelSelector(Map<String, String> labels) {
return labels.entrySet().stream().map(en -> en.getKey() + "=" + en.getValue()).collect(Collectors.joining(","));
}
private interface CheckedSupplier<T> {
T get() throws Exception;
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.tests.commons.junit_extension;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
/**
* This is mainly needed for our pipeline, to get the test classes names. Its purpose is
* to act like a 'dry-run': show all the tests fully qualified names without actually
* running them.
*
* The way to use it: "mvn clean test -Dspring.cloud.k8s.skip.tests=true". This way all
* tests will be skipped, but also will be printed to the standard output as a fully
* qualified name, i.e.:
*
* <pre>
* spring.cloud.k8s.test.to.run -> org.springframework.cloud.kubernetes.Fabric8InsideHealthIndicatorTest
* </pre>
*
* @author wind57
*/
@Deprecated
public class DisabledTestsCondition implements ExecutionCondition {
private static final boolean SKIP_RUNNING_TESTS = "true".equals(System.getProperty("spring.cloud.k8s.skip.tests"));
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext extensionContext) {
if (SKIP_RUNNING_TESTS) {
System.out.println(
"\nspring.cloud.k8s.test.to.run -> " + extensionContext.getRequiredTestClass().getName() + " \n");
return ConditionEvaluationResult.disabled("");
}
else {
return ConditionEvaluationResult.enabled("");
}
}
}

View File

@@ -1,94 +0,0 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.tests.discovery;
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.platform.engine.discovery.DiscoverySelectors;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.TestIdentifier;
import org.junit.platform.launcher.TestPlan;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
/**
* @author wind57
*/
public class TestsDiscovery {
public static void main(String[] args) throws Exception {
List<String> targetClasses = entireClasspath().stream().filter(x -> x.contains("target/classes")).toList();
List<String> targetTestClasses = targetClasses.stream().map(x -> x.replace("classes", "test-classes")).toList();
List<String> jars = entireClasspath().stream().filter(x -> x.contains(".jar")).toList();
List<URL> urls = Stream.of(targetClasses, targetTestClasses, jars).flatMap(List::stream)
.map(x -> toURL(new File(x).toPath().toUri())).toList();
Set<Path> paths = Stream.of(targetClasses, targetTestClasses, jars).flatMap(List::stream).map(Paths::get)
.collect(Collectors.toSet());
replaceClassloader(urls);
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
.selectors(DiscoverySelectors.selectClasspathRoots(paths)).build();
try (LauncherSession session = LauncherFactory.openSession()) {
Launcher launcher = session.getLauncher();
TestPlan testPlan = launcher.discover(request);
testPlan.getRoots().stream().flatMap(x -> testPlan.getChildren(x).stream())
.map(TestIdentifier::getLegacyReportingName).sorted().forEach(test -> {
System.out.println("spring.cloud.k8s.test.to.run -> " + test);
});
}
}
private static void replaceClassloader(List<URL> classpathURLs) {
ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
URLClassLoader classLoader = URLClassLoader.newInstance(classpathURLs.toArray(new URL[0]), parentClassLoader);
Thread.currentThread().setContextClassLoader(classLoader);
}
// /tmp/deps.txt are created by the pipeline
private static List<String> entireClasspath() throws Exception {
try (Stream<String> lines = Files.lines(Paths.get("/tmp/deps.txt"))) {
return lines.distinct().collect(Collectors.toList());
}
}
private static URL toURL(URI uri) {
try {
return uri.toURL();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -1 +0,0 @@
org.springframework.cloud.kubernetes.tests.commons.junit_extension.DisabledTestsCondition

View File

@@ -1,22 +0,0 @@
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:1.35
command: ["/bin/sh"]
args: ["-c", "sleep 100000"]

View File

@@ -1,12 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: busybox-service
spec:
selector:
app: busybox
type: ClusterIP
ports:
- name: busybox-port
port: 8080
targetPort: 80

View File

@@ -1,14 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
app: spring-cloud-kubernetes-core-k8s-client-it
name: spring-cloud-kubernetes-cluster-role-binding
roleRef:
kind: ClusterRole
apiGroup: rbac.authorization.k8s.io
name: cluster-role
subjects:
- kind: ServiceAccount
name: spring-cloud-kubernetes-serviceaccount
namespace: default

View File

@@ -1,8 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-role
rules:
- apiGroups: ["", "extensions", "apps", "discovery.k8s.io"]
resources: ["configmaps", "pods", "services", "endpoints", "secrets", "endpointslices"]
verbs: ["get", "list", "watch"]

View File

@@ -1,15 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app: spring-cloud-kubernetes-core-k8s-client-it
name: spring-cloud-kubernetes-role-binding
namespace: default
roleRef:
kind: ClusterRole
apiGroup: rbac.authorization.k8s.io
name: cluster-role
subjects:
- kind: ServiceAccount
name: spring-cloud-kubernetes-serviceaccount
namespace: default

View File

@@ -1,6 +0,0 @@
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: integration-test
name: spring-cloud-kubernetes-serviceaccount

View File

@@ -1,14 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app: spring-cloud-kubernetes-core-k8s-client-it
name: istio-test-rb
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: istio-test
subjects:
- kind: ServiceAccount
name: spring-cloud-kubernetes-istio-serviceaccount
namespace: istio-test

View File

@@ -1,9 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: istio-test
name: istio-test
rules:
- apiGroups: [ "", "extensions", "apps" ]
resources: [ "configmaps", "pods", "services", "endpoints", "secrets" ]
verbs: [ "get", "list", "watch" ]

View File

@@ -1,7 +0,0 @@
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: istio-integration-test
name: spring-cloud-kubernetes-istio-serviceaccount
namespace: istio-test

View File

@@ -1,54 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: kafka
component: kafka-broker
name: kafka-broker
spec:
replicas: 1
selector:
matchLabels:
app: kafka
component: kafka-broker
template:
metadata:
labels:
app: kafka
component: kafka-broker
spec:
# otherwise we will get an env var "KAFKA_PORT" (from service name: "kafka" and appended with "_PORT")
# and this will cause this problem: https://github.com/confluentinc/cp-docker-images/blob/master/debian/kafka/include/etc/confluent/docker/configure#L58-L62
# Another solution is to rename the service.
enableServiceLinks: false
containers:
- name: kafka
image: confluentinc/cp-kafka:7.2.1
ports:
- containerPort: 9092
env:
- name: KAFKA_LISTENERS
value: "INTERNAL://0.0.0.0:9092,OUTSIDE://0.0.0.0:9094"
- name: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP
value: "INTERNAL:PLAINTEXT,OUTSIDE:PLAINTEXT"
- name: KAFKA_ADVERTISED_LISTENERS
value: "INTERNAL://kafka:9092,OUTSIDE://localhost:9094"
- name: KAFKA_INTER_BROKER_LISTENER_NAME
value: "INTERNAL"
- name: KAFKA_ADVERTISED_HOST_NAME
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: KAFKA_ZOOKEEPER_CONNECT
value: zookeeper:2181
# we have enabled auto creation of topics and when this happens there is a replication factor of 3
# that is set automatically. Since we don't have that many, producers will fail.
# This setting ensures that there is just one replication
- name: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR
value: "1"

View File

@@ -1,16 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: kafka
labels:
app: kafka
component: kafka-broker
spec:
ports:
- port: 9092
name: kafka-port
targetPort: 9092
protocol: TCP
selector:
app: kafka
component: kafka-broker

View File

@@ -1,24 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: rabbitmq
labels:
app: taskqueue
component: rabbitmq
spec:
selector:
matchLabels:
app: taskqueue
component: rabbitmq
template:
metadata:
labels:
app: taskqueue
component: rabbitmq
spec:
containers:
- name: taskqueue
image: rabbitmq:3-management
imagePullPolicy: IfNotPresent

View File

@@ -1,17 +0,0 @@
apiVersion: v1
kind: Service
metadata:
labels:
component: rabbitmq
name: rabbitmq-service
spec:
ports:
- port: 5672
name: amqp
targetPort: 5672
- port: 15672
name: http-stats
targetPort: 15672
selector:
app: taskqueue
component: rabbitmq

View File

@@ -1,14 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app: spring-cloud-kubernetes-core-k8s-client-it
name: spring-cloud-kubernetes-core-k8s-client-it:view
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: namespace-reader
subjects:
- kind: ServiceAccount
name: spring-cloud-kubernetes-serviceaccount
namespace: default

View File

@@ -1,9 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: namespace-reader
rules:
- apiGroups: ["", "extensions", "apps", "discovery.k8s.io"]
resources: ["configmaps", "pods", "services", "endpoints", "secrets", "endpointslices"]
verbs: ["get", "list", "watch"]

View File

@@ -1,6 +0,0 @@
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: integration-test
name: spring-cloud-kubernetes-serviceaccount

View File

@@ -1,34 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: service-wiremock-deployment
spec:
selector:
matchLabels:
app: service-wiremock
template:
metadata:
labels:
app: service-wiremock
spec:
containers:
- name: service-wiremock
image: wiremock/wiremock:2.35.0
args: ["--verbose"]
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 8080
path: /__admin/mappings
initialDelaySeconds: 3
periodSeconds: 2
failureThreshold: 1
livenessProbe:
httpGet:
port: 8080
path: /__admin/mappings
initialDelaySeconds: 3
periodSeconds: 2
failureThreshold: 1
ports:
- containerPort: 8080

View File

@@ -1,17 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wiremock-ingress
namespace: default
spec:
rules:
- http:
paths:
- path: /wiremock/
pathType: Prefix
backend:
service:
name: service-wiremock
port:
number: 8080

View File

@@ -1,14 +0,0 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: service-wiremock
name: service-wiremock
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app: service-wiremock
type: ClusterIP

View File

@@ -1,31 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: kafka
component: zookeeper
name: zookeeper
spec:
replicas: 1
selector:
matchLabels:
app: kafka
component: zookeeper
template:
metadata:
labels:
app: kafka
component: zookeeper
spec:
containers:
- name: zookeeper
image: confluentinc/cp-zookeeper:7.2.1
ports:
- containerPort: 2181
env:
- name: ZOOKEEPER_ID
value: "1"
- name: ZOOKEEPER_SERVER_1
value: zookeeper
- name: ZOOKEEPER_CLIENT_PORT
value: 2181

View File

@@ -1,16 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: zookeeper
labels:
app: kafka
component: zookeeper
spec:
ports:
- port: 2181
name: zookeeper-port
targetPort: 2181
protocol: TCP
selector:
app: kafka
component: zookeeper