Skip to content

Commit

Permalink
add fabric8 it tests
Browse files Browse the repository at this point in the history
  • Loading branch information
wind57 committed Nov 13, 2024
1 parent c681f0a commit dca7b19
Show file tree
Hide file tree
Showing 8 changed files with 933 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ public static boolean reload(PropertySourceLocator locator, ConfigurableEnvironm

boolean changed = changed(sourceFromK8s, existingSources);
if (changed) {
LOG.info("Detected change in config maps/secrets");
LOG.info("Detected change in config maps/secrets, reload will ne triggered");
return true;
}
else {
LOG.debug("No change detected in config maps/secrets, reload will not happen");
LOG.debug("reloadable condition was not satisfied, reload will not be triggered");
}

return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
/*
* Copyright 2013-2024 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.config;

import io.fabric8.kubernetes.client.KubernetesClient;

import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties;

/**
* Only needed to make Fabric8ConfigMapPropertySourceLocator visible for testing purposes
*
* @author wind57
*/
public class VisibleFabric8ConfigMapPropertySourceLocator extends Fabric8ConfigMapPropertySourceLocator {

public VisibleFabric8ConfigMapPropertySourceLocator(KubernetesClient client, ConfigMapConfigProperties properties, KubernetesNamespaceProvider provider) {
public VisibleFabric8ConfigMapPropertySourceLocator(KubernetesClient client, ConfigMapConfigProperties properties,
KubernetesNamespaceProvider provider) {
super(client, properties, provider);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2013-2024 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.config;

import io.fabric8.kubernetes.client.KubernetesClient;

import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties;

/**
* Only needed to make Fabric8SecretsPropertySourceLocator visible for testing purposes
*
* @author wind57
*/
public class VisibleFabric8SecretsPropertySourceLocator extends Fabric8SecretsPropertySourceLocator {

public VisibleFabric8SecretsPropertySourceLocator(KubernetesClient client, SecretsConfigProperties properties,
KubernetesNamespaceProvider provider) {
super(client, properties, provider);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/*
* Copyright 2012-2024 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.config.reload_it;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Set;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.ConfigMapList;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties;
import org.springframework.cloud.kubernetes.commons.config.RetryProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties;
import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy;
import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator;
import org.springframework.cloud.kubernetes.fabric8.config.VisibleFabric8ConfigMapPropertySourceLocator;
import org.springframework.cloud.kubernetes.fabric8.config.reload.Fabric8EventBasedConfigMapChangeDetector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.mock.env.MockEnvironment;

/**
* set 'spring.cloud.kubernetes.reload.enabled=false' so that auto-configuration does not
* kick in, as we create our own config for the test here.
*
* @author wind57
*/
@SpringBootTest(
properties = { "spring.main.cloud-platform=kubernetes", "spring.main.allow-bean-definition-overriding=true",
"spring.cloud.kubernetes.reload.enabled=false",
"logging.level.org.springframework.cloud.kubernetes.commons.config=debug" },
classes = { EventReloadConfigMapTest.TestConfig.class })
@EnableKubernetesMockClient(crud = true)
@ExtendWith(OutputCaptureExtension.class)
public class EventReloadConfigMapTest {

private static final boolean FAIL_FAST = false;

private static final String CONFIG_MAP_NAME = "mine";

private static final String NAMESPACE = "spring-k8s";

private static KubernetesClient kubernetesClient;

private static final boolean[] strategyCalled = new boolean[] { false };

@BeforeAll
static void beforeAll() {

kubernetesClient = Mockito.spy(kubernetesClient);
kubernetesClient.getConfiguration().setRequestRetryBackoffLimit(0);

ConfigMap configMapOne = configMap(CONFIG_MAP_NAME, Map.of());

// for the informer, when it starts
kubernetesClient.configMaps().inNamespace(NAMESPACE).resource(configMapOne).create();
}

@Test
@SuppressWarnings({ "unchecked" })
void test(CapturedOutput output) {

// we need to create this one before mocking calls
NonNamespaceOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> operation = kubernetesClient.configMaps()
.inNamespace(NAMESPACE);

// makes sure that when 'onEvent' is triggered (because we added a config map)
// the call to /api/v1/namespaces/spring-k8s/configmaps will fail with an
// Exception
MixedOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> mixedOperation = Mockito
.mock(MixedOperation.class);
NonNamespaceOperation<ConfigMap, ConfigMapList, Resource<ConfigMap>> mockedOperation = Mockito
.mock(NonNamespaceOperation.class);
Mockito.when(kubernetesClient.configMaps()).thenReturn(mixedOperation);
Mockito.when(mixedOperation.inNamespace(NAMESPACE)).thenReturn(mockedOperation);
Mockito.when(mockedOperation.list()).thenThrow(new RuntimeException("failed in reading configmap"));

// create a different configmap that triggers even based reloading.
// the one we create, will trigger a call to
// /api/v1/namespaces/spring-k8s/configmaps
// that we mocked above to fail.
ConfigMap configMapTwo = configMap("not" + CONFIG_MAP_NAME, Map.of("a", "b"));
operation.resource(configMapTwo).create();

// we fail while reading 'configMapTwo'
Awaitility.await().atMost(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(1)).until(() -> {
boolean one = output.getOut().contains("failure in reading named sources");
boolean two = output.getOut()
.contains("there was an error while reading config maps/secrets, no reload will happen");
boolean three = output.getOut()
.contains("reloadable condition was not satisfied, reload will not be triggered");
boolean updateStrategyNotCalled = !strategyCalled[0];
return one && two && three && updateStrategyNotCalled;
});

// reset the mock and replace our configmap with some data, so that reload
// is triggered
Mockito.reset(kubernetesClient);
ConfigMap configMapOne = configMap(CONFIG_MAP_NAME, Map.of("a", "b"));
operation.resource(configMapOne).replace();

// it passes while reading 'configMapThatWillPass'
Awaitility.await()
.atMost(Duration.ofSeconds(10))
.pollInterval(Duration.ofSeconds(1))
.until(() -> strategyCalled[0]);
}

private static ConfigMap configMap(String name, Map<String, String> data) {
return new ConfigMapBuilder().withNewMetadata().withName(name).endMetadata().withData(data).build();
}

@TestConfiguration
static class TestConfig {

@Bean
@Primary
Fabric8EventBasedConfigMapChangeDetector fabric8EventBasedSecretsChangeDetector(AbstractEnvironment environment,
ConfigReloadProperties configReloadProperties, ConfigurationUpdateStrategy configurationUpdateStrategy,
Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator,
KubernetesNamespaceProvider namespaceProvider) {
return new Fabric8EventBasedConfigMapChangeDetector(environment, configReloadProperties, kubernetesClient,
configurationUpdateStrategy, fabric8ConfigMapPropertySourceLocator, namespaceProvider);
}

@Bean
@Primary
AbstractEnvironment environment() {
MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty("spring.cloud.kubernetes.client.namespace", NAMESPACE);

// simulate that environment already has a Fabric8ConfigMapPropertySource,
// otherwise we can't properly test reload functionality
ConfigMapConfigProperties configMapConfigProperties = new ConfigMapConfigProperties(true, List.of(),
List.of(), Map.of(), true, CONFIG_MAP_NAME, NAMESPACE, false, true, true, RetryProperties.DEFAULT);
KubernetesNamespaceProvider namespaceProvider = new KubernetesNamespaceProvider(mockEnvironment);

PropertySource<?> propertySource = new VisibleFabric8ConfigMapPropertySourceLocator(kubernetesClient,
configMapConfigProperties, namespaceProvider)
.locate(mockEnvironment);

mockEnvironment.getPropertySources().addFirst(propertySource);
return mockEnvironment;
}

@Bean
@Primary
ConfigReloadProperties configReloadProperties() {
return new ConfigReloadProperties(true, true, false, ConfigReloadProperties.ReloadStrategy.REFRESH,
ConfigReloadProperties.ReloadDetectionMode.EVENT, Duration.ofMillis(2000), Set.of(NAMESPACE), false,
Duration.ofSeconds(2));
}

@Bean
@Primary
ConfigMapConfigProperties configMapConfigProperties() {
return new ConfigMapConfigProperties(true, List.of(), List.of(), Map.of(), true, CONFIG_MAP_NAME, NAMESPACE,
false, true, FAIL_FAST, RetryProperties.DEFAULT);
}

@Bean
@Primary
KubernetesNamespaceProvider namespaceProvider(AbstractEnvironment environment) {
return new KubernetesNamespaceProvider(environment);
}

@Bean
@Primary
ConfigurationUpdateStrategy configurationUpdateStrategy() {
return new ConfigurationUpdateStrategy("to-console", () -> {
strategyCalled[0] = true;
});
}

@Bean
@Primary
Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator(
ConfigMapConfigProperties configMapConfigProperties, KubernetesNamespaceProvider namespaceProvider) {
return new VisibleFabric8ConfigMapPropertySourceLocator(kubernetesClient, configMapConfigProperties,
namespaceProvider);
}

}

}
Loading

0 comments on commit dca7b19

Please sign in to comment.