Merge branch '3.0.x'

This commit is contained in:
Ryan Baxter
2023-08-21 10:57:45 -07:00
12 changed files with 903 additions and 913 deletions

View File

@@ -0,0 +1,180 @@
/*
* 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.fabric8.discovery;
import java.util.List;
import org.assertj.core.api.Assertions;
import org.testcontainers.k3s.K3sContainer;
import org.springframework.boot.test.json.BasicJsonTester;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.WebClient;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.builder;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.retrySpec;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.waitForLogStatement;
/**
* @author wind57
*/
final class Fabric8DiscoveryClientHealthDelegate {
private Fabric8DiscoveryClientHealthDelegate() {
}
private static final String REACTIVE_STATUS = "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].status";
private static final String BLOCKING_STATUS = "$.components.discoveryComposite.components.discoveryClient.status";
private static final BasicJsonTester BASIC_JSON_TESTER = new BasicJsonTester(
Fabric8DiscoveryClientHealthDelegate.class);
/**
* Reactive is disabled, only blocking is active. As such,
* KubernetesInformerDiscoveryClientAutoConfiguration::indicatorInitializer will post
* an InstanceRegisteredEvent.
*
* We assert for logs and call '/health' endpoint to see that blocking discovery
* client was initialized.
*/
static void testBlockingConfiguration(K3sContainer k3sContainer, String imageName) {
waitForLogStatement("Will publish InstanceRegisteredEvent from blocking implementation", k3sContainer,
imageName);
waitForLogStatement("publishing InstanceRegisteredEvent", k3sContainer, imageName);
waitForLogStatement("Discovery Client has been initialized", k3sContainer, imageName);
waitForLogStatement(
"received InstanceRegisteredEvent from pod with 'app' label value : spring-cloud-kubernetes-fabric8-client-discovery",
k3sContainer, imageName);
WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build();
String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
.retryWhen(retrySpec()).block();
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(BLOCKING_STATUS)
.isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathArrayValue(
"$.components.discoveryComposite.components.discoveryClient.details.services")
.containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes",
"busybox-service", "external-name-service", "service-wiremock");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(REACTIVE_STATUS);
}
/**
* Both blocking and reactive are enabled.
*/
static void testDefaultConfiguration(K3sContainer k3sContainer, String imageName) {
waitForLogStatement("Will publish InstanceRegisteredEvent from blocking implementation", k3sContainer,
imageName);
waitForLogStatement("publishing InstanceRegisteredEvent", k3sContainer, imageName);
waitForLogStatement("Discovery Client has been initialized", k3sContainer, imageName);
waitForLogStatement("received InstanceRegisteredEvent from pod with 'app' label value : "
+ "spring-cloud-kubernetes-fabric8-client-discovery", k3sContainer, imageName);
WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build();
String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
.retryWhen(retrySpec()).block();
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathStringValue("$.components.discoveryComposite.components.discoveryClient.status")
.isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathArrayValue(
"$.components.discoveryComposite.components.discoveryClient.details.services")
.containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes",
"external-name-service", "service-wiremock", "busybox-service");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status").isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(
"$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].status")
.isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue(
"$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].details.services")
.containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes",
"external-name-service", "service-wiremock", "busybox-service");
}
/**
* Reactive is enabled, blocking is disabled. As such,
* KubernetesInformerDiscoveryClientAutoConfiguration::indicatorInitializer will post
* an InstanceRegisteredEvent.
*
* We assert for logs and call '/health' endpoint to see that blocking discovery
* client was initialized.
*/
static void testReactiveConfiguration(K3sContainer k3sContainer, String imageName) {
waitForLogStatement("Will publish InstanceRegisteredEvent from reactive implementation", k3sContainer,
imageName);
waitForLogStatement("publishing InstanceRegisteredEvent", k3sContainer, imageName);
waitForLogStatement("Discovery Client has been initialized", k3sContainer, imageName);
waitForLogStatement(
"received InstanceRegisteredEvent from pod with 'app' label value : spring-cloud-kubernetes-fabric8-client-discovery",
k3sContainer, imageName);
WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build();
String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
.retryWhen(retrySpec()).block();
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status").isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(REACTIVE_STATUS)
.isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue(
"$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].details.services")
.containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes",
"external-name-service", "service-wiremock", "busybox-service");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(BLOCKING_STATUS);
// test for services also:
WebClient servicesClient = builder().baseUrl("http://localhost/reactive/services").build();
List<String> servicesResult = servicesClient.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<String>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertThat(servicesResult).contains("spring-cloud-kubernetes-fabric8-client-discovery");
Assertions.assertThat(servicesResult).contains("kubernetes");
}
}

View File

@@ -1,318 +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.fabric8.discovery;
import java.io.InputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Container;
import org.testcontainers.k3s.K3sContainer;
import reactor.netty.http.client.HttpClient;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;
import org.springframework.boot.test.json.BasicJsonTester;
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
/**
* @author wind57
*/
class Fabric8DiscoveryClientHealthIT {
private static final String REACTIVE_STATUS = "$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].status";
private static final String BLOCKING_STATUS = "$.components.discoveryComposite.components.discoveryClient.status";
private static final String NAMESPACE = "default";
private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery";
private static KubernetesClient client;
private static final BasicJsonTester BASIC_JSON_TESTER = new BasicJsonTester(Fabric8DiscoveryClientHealthIT.class);
private static Util util;
private static final K3sContainer K3S = Commons.container();
@BeforeAll
static void beforeAll() throws Exception {
K3S.start();
Commons.validateImage(IMAGE_NAME, K3S);
Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S);
util = new Util(K3S);
client = util.client();
util.setUp(NAMESPACE);
}
@AfterAll
static void after() throws Exception {
Commons.cleanUp(IMAGE_NAME, K3S);
Commons.systemPrune();
}
/**
* Reactive is disabled, only blocking is active. As such,
* KubernetesInformerDiscoveryClientAutoConfiguration::indicatorInitializer will post
* an InstanceRegisteredEvent.
*
* We assert for logs and call '/health' endpoint to see that blocking discovery
* client was initialized.
*/
@Test
void testBlockingConfiguration() {
manifests(true, false, Phase.CREATE);
assertLogStatement("Will publish InstanceRegisteredEvent from blocking implementation");
assertLogStatement("publishing InstanceRegisteredEvent");
assertLogStatement("Discovery Client has been initialized");
assertLogStatement(
"received InstanceRegisteredEvent from pod with 'app' label value : spring-cloud-kubernetes-fabric8-client-discovery");
WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build();
String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
.retryWhen(retrySpec()).block();
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(BLOCKING_STATUS)
.isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathArrayValue(
"$.components.discoveryComposite.components.discoveryClient.details.services")
.containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(REACTIVE_STATUS);
manifests(true, false, Phase.DELETE);
}
/**
* Both blocking and reactive are enabled.
*/
@Test
void testDefaultConfiguration() {
manifests(false, false, Phase.CREATE);
assertLogStatement("Will publish InstanceRegisteredEvent from blocking implementation");
assertLogStatement("publishing InstanceRegisteredEvent");
assertLogStatement("Discovery Client has been initialized");
assertLogStatement("received InstanceRegisteredEvent from pod with 'app' label value : "
+ "spring-cloud-kubernetes-fabric8-client-discovery");
WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build();
String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
.retryWhen(retrySpec()).block();
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathStringValue("$.components.discoveryComposite.status").isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathStringValue("$.components.discoveryComposite.components.discoveryClient.status")
.isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathArrayValue(
"$.components.discoveryComposite.components.discoveryClient.details.services")
.containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status").isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(
"$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].status")
.isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue(
"$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].details.services")
.containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes");
manifests(false, false, Phase.DELETE);
}
/**
* Reactive is enabled, blocking is disabled. As such,
* KubernetesInformerDiscoveryClientAutoConfiguration::indicatorInitializer will post
* an InstanceRegisteredEvent.
*
* We assert for logs and call '/health' endpoint to see that blocking discovery
* client was initialized.
*/
@Test
void testReactiveConfiguration() {
manifests(false, true, Phase.CREATE);
assertLogStatement("Will publish InstanceRegisteredEvent from reactive implementation");
assertLogStatement("publishing InstanceRegisteredEvent");
assertLogStatement("Discovery Client has been initialized");
assertLogStatement(
"received InstanceRegisteredEvent from pod with 'app' label value : spring-cloud-kubernetes-fabric8-client-discovery");
WebClient healthClient = builder().baseUrl("http://localhost/actuator/health").build();
String healthResult = healthClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
.retryWhen(retrySpec()).block();
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
.extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status").isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathStringValue(REACTIVE_STATUS)
.isEqualTo("UP");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue(
"$.components.reactiveDiscoveryClients.components.['Fabric8 Kubernetes Reactive Discovery Client'].details.services")
.containsExactlyInAnyOrder("spring-cloud-kubernetes-fabric8-client-discovery", "kubernetes");
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(BLOCKING_STATUS);
// test for services also:
WebClient servicesClient = builder().baseUrl("http://localhost/reactive/services").build();
List<String> servicesResult = servicesClient.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<String>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertThat(servicesResult).contains("spring-cloud-kubernetes-fabric8-client-discovery");
Assertions.assertThat(servicesResult).contains("kubernetes");
manifests(false, true, Phase.DELETE);
}
private static void manifests(boolean disableReactive, boolean disableBlocking, Phase phase) {
InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml");
InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml");
InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml");
Deployment deployment = client.apps().deployments().load(deploymentStream).item();
List<EnvVar> envVars = new ArrayList<>(
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv());
EnvVar debugLevelForCommons = new EnvVarBuilder()
.withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_DISCOVERY").withValue("DEBUG")
.build();
if (!disableBlocking) {
EnvVar debugBlockingEnvVar = new EnvVarBuilder()
.withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH").withValue("DEBUG")
.build();
EnvVar debugLevelForBlocking = new EnvVarBuilder()
.withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY").withValue("DEBUG")
.build();
envVars.add(debugBlockingEnvVar);
envVars.add(debugLevelForBlocking);
}
if (!disableReactive) {
EnvVar debugReactiveEnvVar = new EnvVarBuilder()
.withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH_REACTIVE")
.withValue("DEBUG").build();
EnvVar debugLevelForReactive = new EnvVarBuilder()
.withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY_REACTIVE")
.withValue("DEBUG").build();
EnvVar debugLevelForBlocking = new EnvVarBuilder()
.withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY").withValue("DEBUG")
.build();
envVars.add(debugReactiveEnvVar);
envVars.add(debugLevelForBlocking);
envVars.add(debugLevelForReactive);
}
Service service = client.services().load(serviceStream).item();
Ingress ingress = client.network().v1().ingresses().load(ingressStream).item();
if (disableBlocking) {
EnvVar disableBlockingEnvVar = new EnvVarBuilder().withName("SPRING_CLOUD_DISCOVERY_BLOCKING_ENABLED")
.withValue("FALSE").build();
envVars.add(disableBlockingEnvVar);
}
if (disableReactive) {
EnvVar disableReactiveEnvVar = new EnvVarBuilder().withName("SPRING_CLOUD_DISCOVERY_REACTIVE_ENABLED")
.withValue("FALSE").build();
envVars.add(disableReactiveEnvVar);
}
envVars.add(debugLevelForCommons);
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
if (phase.equals(Phase.CREATE)) {
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
}
else {
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
}
}
private WebClient.Builder builder() {
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
}
private RetryBackoffSpec retrySpec() {
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
}
private void assertLogStatement(String message) {
try {
String appPodName = K3S.execInContainer("sh", "-c",
"kubectl get pods -l app=" + IMAGE_NAME + " -o=name --no-headers | tr -d '\n'").getStdout();
Container.ExecResult execResult = K3S.execInContainer("sh", "-c", "kubectl logs " + appPodName.trim());
String ok = execResult.getStdout();
Assertions.assertThat(ok).contains(message);
}
catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,289 @@
/*
* 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.fabric8.discovery;
import java.time.Duration;
import java.util.Objects;
import org.testcontainers.k3s.K3sContainer;
import reactor.netty.http.client.HttpClient;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import static org.awaitility.Awaitility.await;
/**
* @author wind57
*/
final class Fabric8DiscoveryClientUtil {
private Fabric8DiscoveryClientUtil() {
}
static final String BODY_ONE = """
{
"spec": {
"template": {
"spec": {
"containers": [{
"name": "spring-cloud-kubernetes-fabric8-client-discovery",
"image": "image_name_here",
"env": [
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY",
"value": "DEBUG"
},
{
"name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_INCLUDEEXTERNALNAMESERVICES",
"value": "TRUE"
}
]
}]
}
}
}
}
""";
static final String BODY_TWO = """
{
"spec": {
"template": {
"spec": {
"containers": [{
"name": "spring-cloud-kubernetes-fabric8-client-discovery",
"image": "image_name_here",
"env": [
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_DISCOVERY",
"value": "DEBUG"
},
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH",
"value": "DEBUG"
},
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY",
"value": "DEBUG"
},
{
"name": "SPRING_CLOUD_DISCOVERY_REACTIVE_ENABLED",
"value": "FALSE"
}
]
}]
}
}
}
}
""";
static final String BODY_THREE = """
{
"spec": {
"template": {
"spec": {
"containers": [{
"name": "spring-cloud-kubernetes-fabric8-client-discovery",
"image": "image_name_here",
"env": [
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_DISCOVERY",
"value": "DEBUG"
},
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH_REACTIVE",
"value": "DEBUG"
},
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY_REACTIVE",
"value": "DEBUG"
},
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY",
"value": "DEBUG"
},
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH",
"value": "DEBUG"
},
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY",
"value": "DEBUG"
}
]
}]
}
}
}
}
""";
static final String BODY_FOUR = """
{
"spec": {
"template": {
"spec": {
"containers": [{
"name": "spring-cloud-kubernetes-fabric8-client-discovery",
"image": "image_name_here",
"env": [
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_DISCOVERY",
"value": "DEBUG"
},
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_CLIENT_DISCOVERY_HEALTH_REACTIVE",
"value": "DEBUG"
},
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY_REACTIVE",
"value": "DEBUG"
},
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY",
"value": "DEBUG"
},
{
"name": "SPRING_CLOUD_DISCOVERY_BLOCKING_ENABLED",
"value": "FALSE"
}
]
}]
}
}
}
}
""";
static final String BODY_FIVE = """
{
"spec": {
"template": {
"spec": {
"containers": [{
"name": "spring-cloud-kubernetes-fabric8-client-discovery",
"image": "image_name_here",
"env": [
{
"name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0",
"value": "a-uat"
},
{
"name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_1",
"value": "b-uat"
},
{
"name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_FILTER",
"value": "#root.metadata.namespace matches '^.*uat$'"
},
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY",
"value": "DEBUG"
}
]
}]
}
}
}
}
""";
static final String BODY_SIX = """
{
"spec": {
"template": {
"spec": {
"containers": [{
"name": "spring-cloud-kubernetes-fabric8-client-discovery",
"image": "image_name_here",
"env": [
{
"name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0",
"value": "a-uat"
},
{
"name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_1",
"value": "b-uat"
},
{
"name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_FILTER",
"value": "#root.metadata.namespace matches 'a-uat$'"
},
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY",
"value": "DEBUG"
}
]
}]
}
}
}
}
""";
static final String BODY_SEVEN = """
{
"spec": {
"template": {
"spec": {
"containers": [{
"name": "spring-cloud-kubernetes-fabric8-client-discovery",
"image": "image_name_here",
"env": [
{
"name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0",
"value": "namespace-left"
}
]
}]
}
}
}
}
""";
static WebClient.Builder builder() {
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
}
static RetryBackoffSpec retrySpec() {
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
}
static void waitForLogStatement(String message, K3sContainer k3sContainer, String imageName) {
try {
String appPodName = k3sContainer.execInContainer("sh", "-c",
"kubectl get pods -l app=" + imageName + " -o=name --no-headers | tr -d '\n'").getStdout();
await().atMost(Duration.ofMinutes(2)).pollInterval(Duration.ofSeconds(2)).until(() -> {
String execResult = k3sContainer.execInContainer("sh", "-c", "kubectl logs " + appPodName.trim())
.getStdout();
return execResult.contains(message);
});
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.fabric8.discovery;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.WebClient;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.builder;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.retrySpec;
/**
* @author wind57
*/
final class Fabric8DiscoveryDelegate {
private Fabric8DiscoveryDelegate() {
}
/**
* KubernetesDiscoveryClient::getServices call must include the external-name-service
* also.
*/
static void testAllServices() {
WebClient client = builder().baseUrl("http://localhost/services").build();
List<String> result = client.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<String>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(result.size(), 5);
Assertions.assertTrue(result.contains("kubernetes"));
Assertions.assertTrue(result.contains("spring-cloud-kubernetes-fabric8-client-discovery"));
Assertions.assertTrue(result.contains("service-wiremock"));
Assertions.assertTrue(result.contains("busybox-service"));
Assertions.assertTrue(result.contains("external-name-service"));
}
static void testExternalNameServiceInstance() {
WebClient client = builder().baseUrl("http://localhost/service-instances/external-name-service").build();
List<DefaultKubernetesServiceInstance> serviceInstances = client.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
DefaultKubernetesServiceInstance result = serviceInstances.get(0);
Assertions.assertEquals(serviceInstances.size(), 1);
Assertions.assertEquals(result.getServiceId(), "external-name-service");
Assertions.assertNotNull(result.getInstanceId());
Assertions.assertEquals(result.getHost(), "spring.io");
Assertions.assertEquals(result.getPort(), -1);
Assertions.assertEquals(result.getMetadata(), Map.of("k8s_namespace", "default", "type", "ExternalName"));
Assertions.assertFalse(result.isSecure());
Assertions.assertEquals(result.getUri().toASCIIString(), "spring.io");
Assertions.assertEquals(result.getScheme(), "http");
}
}

View File

@@ -0,0 +1,127 @@
/*
* 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.fabric8.discovery;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.WebClient;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.builder;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.retrySpec;
final class Fabric8DiscoveryFilterDelegate {
private Fabric8DiscoveryFilterDelegate() {
}
/**
* <pre>
* - service "wiremock" is present in namespace "a-uat"
* - service "wiremock" is present in namespace "b-uat"
*
* - we search with a predicate : "#root.metadata.namespace matches '^uat.*$'"
*
* As such, both services are found via 'getInstances' call.
* </pre>
*/
static void filterMatchesBothNamespacesViaThePredicate() {
WebClient clientServices = builder().baseUrl("http://localhost/services").build();
@SuppressWarnings("unchecked")
List<String> services = (List<String>) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
.retryWhen(retrySpec()).block();
Assertions.assertEquals(services.size(), 1);
Assertions.assertTrue(services.contains("service-wiremock"));
WebClient client = builder().baseUrl("http://localhost/service-instances/service-wiremock").build();
List<DefaultKubernetesServiceInstance> serviceInstances = client.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(serviceInstances.size(), 2);
List<DefaultKubernetesServiceInstance> sorted = serviceInstances.stream()
.sorted(Comparator.comparing(DefaultKubernetesServiceInstance::getNamespace)).toList();
DefaultKubernetesServiceInstance first = sorted.get(0);
Assertions.assertEquals(first.getServiceId(), "service-wiremock");
Assertions.assertNotNull(first.getInstanceId());
Assertions.assertEquals(first.getPort(), 8080);
Assertions.assertEquals(first.getNamespace(), "a-uat");
Assertions.assertEquals(first.getMetadata(),
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP"));
DefaultKubernetesServiceInstance second = sorted.get(1);
Assertions.assertEquals(second.getServiceId(), "service-wiremock");
Assertions.assertNotNull(second.getInstanceId());
Assertions.assertEquals(second.getPort(), 8080);
Assertions.assertEquals(second.getNamespace(), "b-uat");
Assertions.assertEquals(second.getMetadata(),
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "b-uat", "type", "ClusterIP"));
}
/**
* <pre>
* - service "wiremock" is present in namespace "a-uat"
* - service "wiremock" is present in namespace "b-uat"
*
* - we search with a predicate : "#root.metadata.namespace matches 'a-uat$'"
*
* As such, only service from 'a-uat' namespace matches.
* </pre>
*/
static void filterMatchesOneNamespaceViaThePredicate() {
WebClient clientServices = builder().baseUrl("http://localhost/services").build();
@SuppressWarnings("unchecked")
List<String> services = (List<String>) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
.retryWhen(retrySpec()).block();
Assertions.assertEquals(services.size(), 1);
Assertions.assertTrue(services.contains("service-wiremock"));
WebClient client = builder().baseUrl("http://localhost/service-instances/service-wiremock").build();
List<DefaultKubernetesServiceInstance> serviceInstances = client.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(serviceInstances.size(), 1);
DefaultKubernetesServiceInstance first = serviceInstances.get(0);
Assertions.assertEquals(first.getServiceId(), "service-wiremock");
Assertions.assertNotNull(first.getInstanceId());
Assertions.assertEquals(first.getPort(), 8080);
Assertions.assertEquals(first.getNamespace(), "a-uat");
Assertions.assertEquals(first.getMetadata(),
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP"));
}
}

View File

@@ -1,257 +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.fabric8.discovery;
import java.io.InputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.k3s.K3sContainer;
import reactor.netty.http.client.HttpClient;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;
import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance;
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
class Fabric8DiscoveryFilterIT {
private static final String FILTER_BOTH_NAMESPACES = "#root.metadata.namespace matches '^.*uat$'";
private static final String FILTER_SINGLE_NAMESPACE = "#root.metadata.namespace matches 'a-uat$'";
private static final String NAMESPACE_A_UAT = "a-uat";
private static final String NAMESPACE_B_UAT = "b-uat";
private static final String NAMESPACE = "default";
private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery";
private static KubernetesClient client;
private static Util util;
private static final K3sContainer K3S = Commons.container();
@BeforeAll
static void beforeAll() throws Exception {
K3S.start();
Commons.validateImage(IMAGE_NAME, K3S);
Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S);
util = new Util(K3S);
client = util.client();
util.setUp(NAMESPACE);
}
@AfterAll
static void afterAll() {
Commons.systemPrune();
}
@BeforeEach
void beforeEach() {
util.createNamespace(NAMESPACE_A_UAT);
util.createNamespace(NAMESPACE_B_UAT);
util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.CREATE);
util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.CREATE);
}
@AfterEach
void afterEach() {
util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.DELETE);
util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.DELETE);
util.deleteNamespace(NAMESPACE_A_UAT);
util.deleteNamespace(NAMESPACE_B_UAT);
}
@AfterAll
static void after() throws Exception {
Commons.cleanUp(IMAGE_NAME, K3S);
}
/**
* <pre>
* - service "wiremock" is present in namespace "a-uat"
* - service "wiremock" is present in namespace "b-uat"
*
* - we search with a predicate : "#root.metadata.namespace matches '^uat.*$'"
*
* As such, both services are found via 'getInstances' call.
* </pre>
*/
@Test
void filterMatchesBothNamespacesViaThePredicate() {
manifests(Phase.CREATE, FILTER_BOTH_NAMESPACES);
WebClient clientServices = builder().baseUrl("http://localhost/services").build();
@SuppressWarnings("unchecked")
List<String> services = (List<String>) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
.retryWhen(retrySpec()).block();
Assertions.assertEquals(services.size(), 1);
Assertions.assertTrue(services.contains("service-wiremock"));
WebClient client = builder().baseUrl("http://localhost/service-instances/service-wiremock").build();
List<DefaultKubernetesServiceInstance> serviceInstances = client.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(serviceInstances.size(), 2);
List<DefaultKubernetesServiceInstance> sorted = serviceInstances.stream()
.sorted(Comparator.comparing(DefaultKubernetesServiceInstance::getNamespace)).toList();
DefaultKubernetesServiceInstance first = sorted.get(0);
Assertions.assertEquals(first.getServiceId(), "service-wiremock");
Assertions.assertNotNull(first.getInstanceId());
Assertions.assertEquals(first.getPort(), 8080);
Assertions.assertEquals(first.getNamespace(), "a-uat");
Assertions.assertEquals(first.getMetadata(),
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP"));
DefaultKubernetesServiceInstance second = sorted.get(1);
Assertions.assertEquals(second.getServiceId(), "service-wiremock");
Assertions.assertNotNull(second.getInstanceId());
Assertions.assertEquals(second.getPort(), 8080);
Assertions.assertEquals(second.getNamespace(), "b-uat");
Assertions.assertEquals(second.getMetadata(),
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "b-uat", "type", "ClusterIP"));
manifests(Phase.DELETE, FILTER_BOTH_NAMESPACES);
}
/**
* <pre>
* - service "wiremock" is present in namespace "a-uat"
* - service "wiremock" is present in namespace "b-uat"
*
* - we search with a predicate : "#root.metadata.namespace matches 'a-uat$'"
*
* As such, only service from 'a-uat' namespace matches.
* </pre>
*/
@Test
void filterMatchesOneNamespaceViaThePredicate() {
manifests(Phase.CREATE, FILTER_SINGLE_NAMESPACE);
WebClient clientServices = builder().baseUrl("http://localhost/services").build();
@SuppressWarnings("unchecked")
List<String> services = (List<String>) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
.retryWhen(retrySpec()).block();
Assertions.assertEquals(services.size(), 1);
Assertions.assertTrue(services.contains("service-wiremock"));
WebClient client = builder().baseUrl("http://localhost/service-instances/service-wiremock").build();
List<DefaultKubernetesServiceInstance> serviceInstances = client.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(serviceInstances.size(), 1);
DefaultKubernetesServiceInstance first = serviceInstances.get(0);
Assertions.assertEquals(first.getServiceId(), "service-wiremock");
Assertions.assertNotNull(first.getInstanceId());
Assertions.assertEquals(first.getPort(), 8080);
Assertions.assertEquals(first.getNamespace(), "a-uat");
Assertions.assertEquals(first.getMetadata(),
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP"));
manifests(Phase.DELETE, FILTER_SINGLE_NAMESPACE);
}
private static void manifests(Phase phase, String serviceFilter) {
InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml");
InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml");
InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml");
Deployment deployment = client.apps().deployments().load(deploymentStream).item();
List<EnvVar> envVars = new ArrayList<>(
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv());
EnvVar namespaceAUat = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0")
.withValue(NAMESPACE_A_UAT).build();
EnvVar namespaceBUat = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_1")
.withValue(NAMESPACE_B_UAT).build();
EnvVar filter = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_FILTER")
.withValue(serviceFilter).build();
EnvVar debug = new EnvVarBuilder()
.withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY").withValue("DEBUG")
.build();
envVars.add(namespaceAUat);
envVars.add(namespaceBUat);
envVars.add(filter);
envVars.add(debug);
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
Service service = client.services().load(serviceStream).item();
Ingress ingress = client.network().v1().ingresses().load(ingressStream).item();
if (phase.equals(Phase.CREATE)) {
client.rbac().clusterRoleBindings()
.resource(client.rbac().clusterRoleBindings().load(getAdminRole()).item()).create();
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
}
else {
client.rbac().clusterRoleBindings()
.resource(client.rbac().clusterRoleBindings().load(getAdminRole()).item()).delete();
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
}
}
private static InputStream getAdminRole() {
return util.inputStream("namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml");
}
private WebClient.Builder builder() {
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
}
private RetryBackoffSpec retrySpec() {
return Retry.fixedDelay(15, Duration.ofSeconds(2)).filter(Objects::nonNull);
}
}

View File

@@ -1,171 +0,0 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.fabric8.discovery;
import java.io.InputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.k3s.K3sContainer;
import reactor.netty.http.client.HttpClient;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;
import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance;
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
/**
* @author wind57
*/
class Fabric8DiscoveryIT {
private static final String NAMESPACE = "default";
private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery";
private static KubernetesClient client;
private static Util util;
private static final K3sContainer K3S = Commons.container();
@BeforeAll
static void beforeAll() throws Exception {
K3S.start();
Commons.validateImage(IMAGE_NAME, K3S);
Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S);
util = new Util(K3S);
client = util.client();
util.setUp(NAMESPACE);
manifests(Phase.CREATE);
util.wiremock(NAMESPACE, "/wiremock", Phase.CREATE);
}
@AfterAll
static void after() throws Exception {
util.wiremock(NAMESPACE, "/wiremock", Phase.DELETE);
manifests(Phase.DELETE);
Commons.cleanUp(IMAGE_NAME, K3S);
Commons.systemPrune();
}
/**
* KubernetesDiscoveryClient::getServices call must include the external-name-service
* also.
*/
@Test
void testAllServices() {
WebClient client = builder().baseUrl("http://localhost/services").build();
List<String> result = client.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<String>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(result.size(), 4);
Assertions.assertTrue(result.contains("kubernetes"));
Assertions.assertTrue(result.contains("spring-cloud-kubernetes-fabric8-client-discovery"));
Assertions.assertTrue(result.contains("service-wiremock"));
Assertions.assertTrue(result.contains("external-name-service"));
}
@Test
void testExternalNameServiceInstance() {
WebClient client = builder().baseUrl("http://localhost/service-instances/external-name-service").build();
List<DefaultKubernetesServiceInstance> serviceInstances = client.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
DefaultKubernetesServiceInstance result = serviceInstances.get(0);
Assertions.assertEquals(serviceInstances.size(), 1);
Assertions.assertEquals(result.getServiceId(), "external-name-service");
Assertions.assertNotNull(result.getInstanceId());
Assertions.assertEquals(result.getHost(), "spring.io");
Assertions.assertEquals(result.getPort(), -1);
Assertions.assertEquals(result.getMetadata(), Map.of("k8s_namespace", "default", "type", "ExternalName"));
Assertions.assertFalse(result.isSecure());
Assertions.assertEquals(result.getUri().toASCIIString(), "spring.io");
Assertions.assertEquals(result.getScheme(), "http");
}
private static void manifests(Phase phase) {
InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml");
InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml");
InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml");
InputStream externalNameServiceInputStream = util.inputStream("external-name-service.yaml");
Deployment deployment = client.apps().deployments().load(deploymentStream).item();
List<EnvVar> existing = new ArrayList<>(
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv());
existing.add(new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_INCLUDEEXTERNALNAMESERVICES")
.withValue("true").build());
existing.add(
new EnvVarBuilder().withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY")
.withValue("DEBUG").build());
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(existing);
Service service = client.services().load(serviceStream).item();
Service externalNameService = client.services().load(externalNameServiceInputStream).item();
Ingress ingress = client.network().v1().ingresses().load(ingressStream).item();
if (phase.equals(Phase.CREATE)) {
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
util.createAndWait(NAMESPACE, null, null, externalNameService, null, false);
}
else {
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
util.deleteAndWait(NAMESPACE, null, externalNameService, null);
}
}
private WebClient.Builder builder() {
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
}
private RetryBackoffSpec retrySpec() {
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.fabric8.discovery;
import java.util.List;
import io.fabric8.kubernetes.api.model.Endpoints;
import org.junit.jupiter.api.Assertions;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.WebClient;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.builder;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.retrySpec;
/**
* @author mbialkowski1
*/
final class Fabric8DiscoveryNamespaceDelegate {
private Fabric8DiscoveryNamespaceDelegate() {
}
static void namespaceFilter() {
WebClient clientServices = builder().baseUrl("http://localhost/services").build();
@SuppressWarnings("unchecked")
List<String> services = (List<String>) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
.retryWhen(retrySpec()).block();
Assertions.assertEquals(services.size(), 1);
Assertions.assertTrue(services.contains("service-wiremock"));
WebClient clientEndpoints = builder().baseUrl("http://localhost/endpoints/service-wiremock").build();
List<Endpoints> endpoints = clientEndpoints.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Endpoints>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(endpoints.size(), 1);
Assertions.assertEquals(endpoints.get(0).getMetadata().getNamespace(), "namespace-left");
}
}

View File

@@ -1,160 +0,0 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.fabric8.discovery;
import java.io.InputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.k3s.K3sContainer;
import reactor.netty.http.client.HttpClient;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
/**
* @author mbialkowski1
*/
class Fabric8DiscoveryNamespaceFilterIT {
private static final String NAMESPACE = "default";
private static final String NAMESPACE_LEFT = "namespace-left";
private static final String NAMESPACE_RIGHT = "namespace-right";
private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery";
private static Util util;
private static KubernetesClient client;
private static final K3sContainer K3S = Commons.container();
@BeforeAll
static void beforeAll() throws Exception {
K3S.start();
Commons.validateImage(IMAGE_NAME, K3S);
Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S);
util = new Util(K3S);
client = util.client();
util.setUp(NAMESPACE);
manifests(Phase.CREATE);
util.createNamespace(NAMESPACE_LEFT);
util.createNamespace(NAMESPACE_RIGHT);
util.wiremock(NAMESPACE_LEFT, "/wiremock", Phase.CREATE);
util.wiremock(NAMESPACE_RIGHT, "/wiremock", Phase.CREATE);
}
@AfterAll
static void after() throws Exception {
manifests(Phase.DELETE);
util.wiremock(NAMESPACE_LEFT, "/wiremock", Phase.DELETE);
util.wiremock(NAMESPACE_RIGHT, "/wiremock", Phase.DELETE);
util.deleteNamespace(NAMESPACE_LEFT);
util.deleteNamespace(NAMESPACE_RIGHT);
Commons.cleanUp(IMAGE_NAME, K3S);
Commons.systemPrune();
}
@Test
void test() {
WebClient clientServices = builder().baseUrl("http://localhost/services").build();
@SuppressWarnings("unchecked")
List<String> services = (List<String>) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
.retryWhen(retrySpec()).block();
Assertions.assertEquals(services.size(), 1);
Assertions.assertTrue(services.contains("service-wiremock"));
WebClient clientEndpoints = builder().baseUrl("http://localhost/endpoints/service-wiremock").build();
List<Endpoints> endpoints = clientEndpoints.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Endpoints>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(endpoints.size(), 1);
Assertions.assertEquals(endpoints.get(0).getMetadata().getNamespace(), NAMESPACE_LEFT);
}
private static void manifests(Phase phase) {
InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml");
InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml");
InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml");
Deployment deployment = client.apps().deployments().load(deploymentStream).item();
List<EnvVar> envVars = new ArrayList<>(
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv());
EnvVar activeProfileProperty = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0")
.withValue(NAMESPACE_LEFT).build();
envVars.add(activeProfileProperty);
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
Service service = client.services().load(serviceStream).item();
Ingress ingress = client.network().v1().ingresses().load(ingressStream).item();
if (phase.equals(Phase.CREATE)) {
client.rbac().clusterRoleBindings()
.resource(client.rbac().clusterRoleBindings().load(getAdminRole()).item()).create();
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
}
else {
client.rbac().clusterRoleBindings()
.resource(client.rbac().clusterRoleBindings().load(getAdminRole()).item()).delete();
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
}
}
private static InputStream getAdminRole() {
return util.inputStream("namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml");
}
private WebClient.Builder builder() {
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
}
private RetryBackoffSpec retrySpec() {
return Retry.fixedDelay(15, Duration.ofSeconds(2)).filter(Objects::nonNull);
}
}

View File

@@ -47,15 +47,36 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_FIVE;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_FOUR;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_ONE;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_SEVEN;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_SIX;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_THREE;
import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8DiscoveryClientUtil.BODY_TWO;
import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.pomVersion;
/**
* @author wind57
*/
class Fabric8DiscoveryPodMetadataIT {
private static final String DEPLOYMENT_NAME = "spring-cloud-kubernetes-fabric8-client-discovery-deployment";
private static final String NAMESPACE = "default";
private static final String NAMESPACE_A_UAT = "a-uat";
private static final String NAMESPACE_B_UAT = "b-uat";
private static final String NAMESPACE_LEFT = "namespace-left";
private static final String NAMESPACE_RIGHT = "namespace-right";
private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery";
private static final String DOCKER_IMAGE = "docker.io/springcloud/" + IMAGE_NAME + ":" + pomVersion();
private static KubernetesClient client;
private static Util util;
@@ -74,12 +95,35 @@ class Fabric8DiscoveryPodMetadataIT {
util.setUp(NAMESPACE);
manifests(Phase.CREATE);
util.wiremock(NAMESPACE, "/wiremock", Phase.CREATE);
util.busybox(NAMESPACE, Phase.CREATE);
util.createNamespace(NAMESPACE_A_UAT);
util.createNamespace(NAMESPACE_B_UAT);
util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.CREATE);
util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.CREATE);
util.createNamespace(NAMESPACE_LEFT);
util.createNamespace(NAMESPACE_RIGHT);
util.wiremock(NAMESPACE_LEFT, "/wiremock", Phase.CREATE);
util.wiremock(NAMESPACE_RIGHT, "/wiremock", Phase.CREATE);
}
@AfterAll
static void after() throws Exception {
util.wiremock(NAMESPACE, "/wiremock", Phase.DELETE);
util.busybox(NAMESPACE, Phase.DELETE);
util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.DELETE);
util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.DELETE);
util.deleteNamespace(NAMESPACE_A_UAT);
util.deleteNamespace(NAMESPACE_B_UAT);
util.wiremock(NAMESPACE_LEFT, "/wiremock", Phase.DELETE);
util.wiremock(NAMESPACE_RIGHT, "/wiremock", Phase.DELETE);
util.deleteNamespace(NAMESPACE_LEFT);
util.deleteNamespace(NAMESPACE_RIGHT);
manifests(Phase.DELETE);
Commons.cleanUp(IMAGE_NAME, K3S);
Commons.systemPrune();
@@ -123,15 +167,68 @@ class Fabric8DiscoveryPodMetadataIT {
Map.of("k8s_namespace", "default", "type", "ClusterIP", "port.busybox-port", "80"));
Assertions.assertTrue(withCustomAnnotation.podMetadata().get("annotations").entrySet().stream().anyMatch(
x -> x.getKey().equals("custom-annotation") && x.getValue().equals("custom-annotation-value")));
testAllOther();
}
private void testAllOther() {
testAllServices();
testExternalNameServiceInstance();
testBlockingConfiguration();
testDefaultConfiguration();
testReactiveConfiguration();
filterMatchesBothNamespacesViaThePredicate();
filterMatchesOneNamespaceViaThePredicate();
namespaceFilter();
}
private void testAllServices() {
util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_ONE, Map.of("app", IMAGE_NAME));
Fabric8DiscoveryDelegate.testAllServices();
}
private void testExternalNameServiceInstance() {
Fabric8DiscoveryDelegate.testExternalNameServiceInstance();
}
private void testBlockingConfiguration() {
util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_TWO, Map.of("app", IMAGE_NAME));
Fabric8DiscoveryClientHealthDelegate.testBlockingConfiguration(K3S, IMAGE_NAME);
}
private void testDefaultConfiguration() {
util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_THREE, Map.of("app", IMAGE_NAME));
Fabric8DiscoveryClientHealthDelegate.testDefaultConfiguration(K3S, IMAGE_NAME);
}
private void testReactiveConfiguration() {
util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_FOUR, Map.of("app", IMAGE_NAME));
Fabric8DiscoveryClientHealthDelegate.testReactiveConfiguration(K3S, IMAGE_NAME);
}
private void filterMatchesBothNamespacesViaThePredicate() {
util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_FIVE, Map.of("app", IMAGE_NAME));
Fabric8DiscoveryFilterDelegate.filterMatchesBothNamespacesViaThePredicate();
}
private void filterMatchesOneNamespaceViaThePredicate() {
util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_SIX, Map.of("app", IMAGE_NAME));
Fabric8DiscoveryFilterDelegate.filterMatchesOneNamespaceViaThePredicate();
}
private void namespaceFilter() {
util.patchWithReplace(DOCKER_IMAGE, DEPLOYMENT_NAME, NAMESPACE, BODY_SEVEN, Map.of("app", IMAGE_NAME));
Fabric8DiscoveryNamespaceDelegate.namespaceFilter();
}
private static void manifests(Phase phase) {
InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml");
InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml");
InputStream externalNameServiceStream = util.inputStream("external-name-service.yaml");
InputStream discoveryServiceStream = util.inputStream("fabric8-discovery-service.yaml");
InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml");
Deployment deployment = client.apps().deployments().load(deploymentStream).item();
Deployment deployment = client.apps().deployments().load(deploymentStream).get();
List<EnvVar> existing = new ArrayList<>(
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv());
@@ -144,18 +241,29 @@ class Fabric8DiscoveryPodMetadataIT {
.withValue("DEBUG").build());
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(existing);
Service service = client.services().load(serviceStream).item();
Ingress ingress = client.network().v1().ingresses().load(ingressStream).item();
Service externalServiceName = client.services().load(externalNameServiceStream).get();
Service discoveryService = client.services().load(discoveryServiceStream).get();
Ingress ingress = client.network().v1().ingresses().load(ingressStream).get();
if (phase.equals(Phase.CREATE)) {
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get())
.create();
util.createAndWait(NAMESPACE, IMAGE_NAME, deployment, discoveryService, ingress, true);
util.createAndWait(NAMESPACE, null, null, externalServiceName, null, true);
}
else {
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get())
.delete();
util.deleteAndWait(NAMESPACE, deployment, discoveryService, ingress);
util.deleteAndWait(NAMESPACE, null, externalServiceName, null);
}
}
private static InputStream getAdminRole() {
return util.inputStream("namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml");
}
private WebClient.Builder builder() {
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
}

View File

@@ -20,6 +20,7 @@ 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;
@@ -30,6 +31,7 @@ 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;
@@ -38,6 +40,8 @@ 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;
@@ -46,6 +50,7 @@ import org.testcontainers.k3s.K3sContainer;
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.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;
@@ -366,6 +371,50 @@ public final class Util {
}
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)

View File

@@ -610,7 +610,7 @@ public final class Util {
V1DeploymentList deployments = appsV1Api.listNamespacedDeployment(namespace, null, null, null,
"metadata.name=" + deploymentName, null, null, null, null, null, null);
if (deployments.getItems().size() < 1) {
if (deployments.getItems().isEmpty()) {
fail("No deployment with name " + deploymentName);
}