Initial commit
This commit is contained in:
117
spring-cloud-kubernetes-test-support/.flattened-pom.xml
Normal file
117
spring-cloud-kubernetes-test-support/.flattened-pom.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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("");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
org.springframework.cloud.kubernetes.tests.commons.junit_extension.DisabledTestsCondition
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
@@ -1,6 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: integration-test
|
||||
name: spring-cloud-kubernetes-serviceaccount
|
||||
Binary file not shown.
@@ -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
|
||||
@@ -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" ]
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: istio-integration-test
|
||||
name: spring-cloud-kubernetes-istio-serviceaccount
|
||||
namespace: istio-test
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"]
|
||||
@@ -1,6 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: integration-test
|
||||
name: spring-cloud-kubernetes-serviceaccount
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user