fabric8-configmap-event-reload-patch-refactor part 1 (#1464)

This commit is contained in:
erabii
2023-10-05 15:41:29 +03:00
committed by GitHub
parent 65c7644f66
commit 11fc5d1ebe
3 changed files with 232 additions and 91 deletions

View File

@@ -18,18 +18,11 @@ package org.springframework.cloud.kubernetes.fabric8.configmap.event.reload;
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 java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.Deployment;
@@ -41,18 +34,22 @@ 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.http.HttpMethod;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import static org.awaitility.Awaitility.await;
import static org.springframework.cloud.kubernetes.fabric8.configmap.event.reload.TestUtil.builder;
import static org.springframework.cloud.kubernetes.fabric8.configmap.event.reload.TestUtil.patchOne;
import static org.springframework.cloud.kubernetes.fabric8.configmap.event.reload.TestUtil.patchThree;
import static org.springframework.cloud.kubernetes.fabric8.configmap.event.reload.TestUtil.patchTwo;
import static org.springframework.cloud.kubernetes.fabric8.configmap.event.reload.TestUtil.reCreateConfigMaps;
import static org.springframework.cloud.kubernetes.fabric8.configmap.event.reload.TestUtil.replaceConfigMap;
import static org.springframework.cloud.kubernetes.fabric8.configmap.event.reload.TestUtil.retrySpec;
import static org.springframework.cloud.kubernetes.integration.tests.commons.Commons.pomVersion;
/**
* @author wind57
@@ -61,6 +58,8 @@ class ConfigMapEventReloadIT {
private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-configmap-event-reload";
private static final String DOCKER_IMAGE = "docker.io/springcloud/" + IMAGE_NAME + ":" + pomVersion();
private static final String NAMESPACE = "default";
private static final K3sContainer K3S = Commons.container();
@@ -81,6 +80,8 @@ class ConfigMapEventReloadIT {
util.createNamespace("left");
util.createNamespace("right");
util.setUpClusterWide(NAMESPACE, Set.of("left", "right"));
manifests(Phase.CREATE);
}
@AfterAll
@@ -89,6 +90,8 @@ class ConfigMapEventReloadIT {
util.deleteNamespace("right");
Commons.cleanUp(IMAGE_NAME, K3S);
Commons.systemPrune();
manifests(Phase.DELETE);
}
/**
@@ -101,7 +104,6 @@ class ConfigMapEventReloadIT {
*/
@Test
void testInformFromOneNamespaceEventNotTriggered() {
manifests("one", Phase.CREATE, false);
Commons.assertReloadLogStatements("added configmap informer for namespace",
"added secret informer for namespace", IMAGE_NAME);
@@ -122,30 +124,37 @@ class ConfigMapEventReloadIT {
.withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap").build())
.withData(Map.of("right.value", "right-after-change")).build();
replaceConfigMap(rightConfigMapAfterChange);
// wait dummy for 5 seconds
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
replaceConfigMap(client, rightConfigMapAfterChange, "right");
webClient = builder().baseUrl("http://localhost/left").build();
result = webClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()).block();
// left configmap has not changed, no restart of app has happened
Assertions.assertEquals("left-initial", result);
manifests("one", Phase.DELETE, false);
WebClient finalWebClient = webClient;
await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(30)).until(() -> {
String innerResult = finalWebClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
.retryWhen(retrySpec()).block();
// left configmap has not changed, no restart of app has happened
return "left-initial".equals(innerResult);
});
testInformFromOneNamespaceEventTriggered();
testInform();
testInformFromOneNamespaceEventTriggeredSecretsDisabled();
}
/**
* <pre>
* - there are two namespaces : left and right
* - each of the namespaces has one configmap
* - we watch the "right" namespace and make a change in the configmap in the same namespace
* - as such, event is triggered and we see the updated value
* - there are two namespaces : left and right
* - each of the namespaces has one configmap
* - we watch the "right" namespace and make a change in the configmap in the same
* namespace
* - as such, event is triggered and we see the updated value
* </pre>
*/
@Test
void testInformFromOneNamespaceEventTriggered() {
manifests("two", Phase.CREATE, false);
reCreateConfigMaps(util, client);
patchOne(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE);
Commons.assertReloadLogStatements("added configmap informer for namespace",
"added secret informer for namespace", IMAGE_NAME);
@@ -160,7 +169,7 @@ class ConfigMapEventReloadIT {
.withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap").build())
.withData(Map.of("right.value", "right-after-change")).build();
replaceConfigMap(rightConfigMapAfterChange);
replaceConfigMap(client, rightConfigMapAfterChange, "right");
String[] resultAfterChange = new String[1];
await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> {
@@ -171,22 +180,23 @@ class ConfigMapEventReloadIT {
return innerResult != null;
});
Assertions.assertEquals("right-after-change", resultAfterChange[0]);
manifests("two", Phase.DELETE, false);
}
/**
* <pre>
* - there are two namespaces : left and right (though we do not care about the left one)
* - left has one configmap : left-configmap
* - right has two configmaps: right-configmap, right-configmap-with-label
* - we watch the "right" namespace, but enable tagging; which means that only
* right-configmap-with-label triggers changes.
* - there are two namespaces : left and right (though we do not care about the left
* one)
* - left has one configmap : left-configmap
* - right has two configmaps: right-configmap, right-configmap-with-label
* - we watch the "right" namespace, but enable tagging; which means that only
* right-configmap-with-label triggers changes.
* </pre>
*/
@Test
void testInform() {
manifests("three", Phase.CREATE, false);
reCreateConfigMaps(util, client);
patchTwo(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE);
Commons.assertReloadLogStatements("added configmap informer for namespace",
"added secret informer for namespace", IMAGE_NAME);
@@ -207,15 +217,14 @@ class ConfigMapEventReloadIT {
.withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap").build())
.withData(Map.of("right.value", "right-after-change")).build();
replaceConfigMap(rightConfigMapAfterChange);
// sleep for 5 seconds
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
replaceConfigMap(client, rightConfigMapAfterChange, "right");
// nothing changes in our app, because we are watching only labeled configmaps
rightResult = rightWebClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec())
.block();
Assertions.assertEquals("right-initial", rightResult);
await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(30)).until(() -> {
String innerRightResult = rightWebClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
.retryWhen(retrySpec()).block();
return "right-initial".equals(innerRightResult);
});
// then deploy a new version of right-with-label-configmap
ConfigMap rightWithLabelConfigMapAfterChange = new ConfigMapBuilder()
@@ -223,7 +232,7 @@ class ConfigMapEventReloadIT {
new ObjectMetaBuilder().withNamespace("right").withName("right-configmap-with-label").build())
.withData(Map.of("right.with.label.value", "right-with-label-after-change")).build();
replaceConfigMap(rightWithLabelConfigMapAfterChange);
replaceConfigMap(client, rightWithLabelConfigMapAfterChange, "right");
// since we have changed a labeled configmap, app will restart and pick up the new
// value
@@ -242,22 +251,23 @@ class ConfigMapEventReloadIT {
rightResult = rightWebClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec())
.block();
Assertions.assertEquals("right-after-change", rightResult);
manifests("three", Phase.DELETE, false);
}
/**
* <pre>
* - there are two namespaces : left and right
* - each of the namespaces has one configmap
* - secrets are disabled
* - we watch the "right" namespace and make a change in the configmap in the same namespace
* - as such, event is triggered and we see the updated value
* - there are two namespaces : left and right
* - each of the namespaces has one configmap
* - secrets are disabled
* - we watch the "right" namespace and make a change in the configmap in the same
* namespace
* - as such, event is triggered and we see the updated value
* </pre>
*/
@Test
void testInformFromOneNamespaceEventTriggeredSecretsDisabled() {
manifests("two", Phase.CREATE, true);
reCreateConfigMaps(util, client);
patchThree(util, DOCKER_IMAGE, IMAGE_NAME, NAMESPACE);
Commons.assertReloadLogStatements("added configmap informer for namespace",
"added secret informer for namespace", IMAGE_NAME);
@@ -272,7 +282,7 @@ class ConfigMapEventReloadIT {
.withMetadata(new ObjectMetaBuilder().withNamespace("right").withName("right-configmap").build())
.withData(Map.of("right.value", "right-after-change")).build();
replaceConfigMap(rightConfigMapAfterChange);
replaceConfigMap(client, rightConfigMapAfterChange, "right");
String[] resultAfterChange = new String[1];
await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> {
@@ -284,10 +294,9 @@ class ConfigMapEventReloadIT {
});
Assertions.assertEquals("right-after-change", resultAfterChange[0]);
manifests("two", Phase.DELETE, true);
}
private static void manifests(String activeProfile, Phase phase, boolean secretsDisabled) {
private static void manifests(Phase phase) {
InputStream deploymentStream = util.inputStream("deployment.yaml");
InputStream serviceStream = util.inputStream("service.yaml");
@@ -298,21 +307,6 @@ class ConfigMapEventReloadIT {
Deployment deployment = Serialization.unmarshal(deploymentStream, Deployment.class);
List<EnvVar> envVars = new ArrayList<>(
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv());
EnvVar activeProfileProperty = new EnvVarBuilder().withName("SPRING_PROFILES_ACTIVE").withValue(activeProfile)
.build();
envVars.add(activeProfileProperty);
if (secretsDisabled) {
EnvVar secretsDisabledEnvVar = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_SECRETS_ENABLED")
.withValue("FALSE").build();
envVars.add(secretsDisabledEnvVar);
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
}
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
Service service = Serialization.unmarshal(serviceStream, Service.class);
Ingress ingress = Serialization.unmarshal(ingressStream, Ingress.class);
ConfigMap leftConfigMap = Serialization.unmarshal(leftConfigMapStream, ConfigMap.class);
@@ -322,32 +316,16 @@ class ConfigMapEventReloadIT {
if (phase.equals(Phase.CREATE)) {
util.createAndWait("left", leftConfigMap, null);
util.createAndWait("right", rightConfigMap, null);
if ("three".equals(activeProfile)) {
util.createAndWait("right", rightWithLabelConfigMap, null);
}
util.createAndWait("right", rightWithLabelConfigMap, null);
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
}
else {
util.deleteAndWait("left", leftConfigMap, null);
util.deleteAndWait("right", rightConfigMap, null);
if ("three".equals(activeProfile)) {
util.deleteAndWait("right", rightWithLabelConfigMap, null);
}
util.deleteAndWait("right", rightWithLabelConfigMap, null);
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
}
}
private WebClient.Builder builder() {
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
}
private RetryBackoffSpec retrySpec() {
return Retry.fixedDelay(120, Duration.ofSeconds(2)).filter(Objects::nonNull);
}
private static void replaceConfigMap(ConfigMap configMap) {
client.configMaps().inNamespace("right").resource(configMap).createOrReplace();
}
}

View File

@@ -0,0 +1,161 @@
/*
* 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.configmap.event.reload;
import java.io.InputStream;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.utils.Serialization;
import reactor.netty.http.client.HttpClient;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;
import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
/**
* @author wind57
*/
final class TestUtil {
private static final Map<String, String> POD_LABELS = Map.of("app",
"spring-cloud-kubernetes-fabric8-client-configmap-event-reload");
private static final String BODY_ONE = """
{
"spec": {
"template": {
"spec": {
"containers": [{
"name": "spring-cloud-kubernetes-fabric8-client-configmap-event-reload",
"image": "image_name_here",
"env": [
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY",
"value": "DEBUG"
},
{
"name": "SPRING_PROFILES_ACTIVE",
"value": "two"
}
]
}]
}
}
}
}
""";
private static final String BODY_TWO = """
{
"spec": {
"template": {
"spec": {
"containers": [{
"name": "spring-cloud-kubernetes-fabric8-client-configmap-event-reload",
"image": "image_name_here",
"env": [
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY",
"value": "DEBUG"
},
{
"name": "SPRING_PROFILES_ACTIVE",
"value": "three"
}
]
}]
}
}
}
}
""";
private static final String BODY_THREE = """
{
"spec": {
"template": {
"spec": {
"containers": [{
"name": "spring-cloud-kubernetes-fabric8-client-configmap-event-reload",
"image": "image_name_here",
"env": [
{
"name": "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY",
"value": "DEBUG"
},
{
"name": "SPRING_PROFILES_ACTIVE",
"value": "two"
},
{
"name": "SPRING_CLOUD_KUBERNETES_SECRETS_ENABLED",
"value": "FALSE"
}
]
}]
}
}
}
}
""";
private TestUtil() {
}
static void reCreateConfigMaps(Util util, KubernetesClient client) {
InputStream leftConfigMapStream = util.inputStream("left-configmap.yaml");
InputStream rightConfigMapStream = util.inputStream("right-configmap.yaml");
ConfigMap leftConfigMap = Serialization.unmarshal(leftConfigMapStream, ConfigMap.class);
ConfigMap rightConfigMap = Serialization.unmarshal(rightConfigMapStream, ConfigMap.class);
replaceConfigMap(client, leftConfigMap, "left");
replaceConfigMap(client, rightConfigMap, "right");
}
static void replaceConfigMap(KubernetesClient client, ConfigMap configMap, String namespace) {
client.configMaps().inNamespace(namespace).resource(configMap).createOrReplace();
}
static void patchOne(Util util, String dockerImage, String deploymentName, String namespace) {
util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_ONE, POD_LABELS);
}
static void patchTwo(Util util, String dockerImage, String deploymentName, String namespace) {
util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_TWO, POD_LABELS);
}
static void patchThree(Util util, String dockerImage, String deploymentName, String namespace) {
util.patchWithReplace(dockerImage, deploymentName, namespace, BODY_THREE, POD_LABELS);
}
static WebClient.Builder builder() {
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
}
static RetryBackoffSpec retrySpec() {
return Retry.fixedDelay(120, Duration.ofSeconds(2)).filter(Objects::nonNull);
}
}

View File

@@ -1,7 +1,7 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-kubernetes-fabric8-client-configmap-deployment-event-reload
name: spring-cloud-kubernetes-fabric8-client-configmap-event-reload
spec:
selector:
matchLabels:
@@ -28,4 +28,6 @@ spec:
- containerPort: 8080
env:
- name: LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_CONFIG_RELOAD
value: DEBUG
value: "DEBUG"
- name: SPRING_PROFILES_ACTIVE
value: "one"