Add support for selective namespaces (#1274)

This commit is contained in:
erabii
2023-04-21 02:46:04 +03:00
committed by GitHub
parent 9fee970ef8
commit fc0f6f1a1a
24 changed files with 1921 additions and 201 deletions

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.kubernetes.client;
import java.nio.file.Paths;
import java.util.function.Supplier;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1Pod;
import io.kubernetes.client.util.Config;
@@ -82,10 +83,14 @@ public class KubernetesClientPodUtils implements PodUtils<V1Pod> {
private V1Pod internalGetPod() {
try {
if (isServiceHostEnvVarPresent() && isHostNameEnvVarPresent() && isServiceAccountFound()) {
LOG.debug("reading pod in namespace : " + namespace);
return client.readNamespacedPod(hostName, namespace, null);
}
}
catch (Throwable t) {
if (t instanceof ApiException apiException) {
LOG.warn("error reading pod, with error : " + apiException.getResponseBody());
}
LOG.warn("Failed to get pod with name:[" + hostName + "]. You should look into this if things aren't"
+ " working as you expect. Are you missing serviceaccount permissions?", t);
}

View File

@@ -0,0 +1,61 @@
/*
* 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.client.discovery;
import java.util.Set;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.log.LogAccessor;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* Conditional that checks if our discovery is _not_ based on selective namespaces, i.e.:
* 'spring.cloud.kubernetes.discovery.namespaces' is not set.
*
* @author wind57
*/
public final class ConditionalOnSelectiveNamespacesMissing implements ConfigurationCondition {
private static final LogAccessor LOG = new LogAccessor(
LogFactory.getLog(ConditionalOnSelectiveNamespacesMissing.class));
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Set<String> selectiveNamespaces = Binder.get(context.getEnvironment())
.bind("spring.cloud.kubernetes.discovery.namespaces", Bindable.setOf(String.class)).orElse(Set.of());
boolean selectiveNamespacesMissing = selectiveNamespaces.isEmpty();
if (selectiveNamespacesMissing) {
LOG.debug(() -> "selective namespaces not present");
}
else {
LOG.debug(() -> "found selective namespaces : " + selectiveNamespaces.stream().sorted().toList());
}
return selectiveNamespacesMissing;
}
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.client.discovery;
import java.util.Set;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.log.LogAccessor;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* Conditional that checks if our discovery is based on selective namespaces, i.e.:
* 'spring.cloud.kubernetes.discovery.namespaces' is set.
*
* @author wind57
*/
public final class ConditionalOnSelectiveNamespacesPresent implements ConfigurationCondition {
private static final LogAccessor LOG = new LogAccessor(
LogFactory.getLog(ConditionalOnSelectiveNamespacesPresent.class));
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Set<String> selectiveNamespaces = Binder.get(context.getEnvironment())
.bind("spring.cloud.kubernetes.discovery.namespaces", Bindable.setOf(String.class)).orElse(Set.of());
boolean selectiveNamespacesPresent = !selectiveNamespaces.isEmpty();
if (selectiveNamespacesPresent) {
LOG.debug(() -> "found selective namespaces : " + selectiveNamespaces.stream().sorted().toList());
}
else {
LOG.debug(() -> "selective namespaces not present");
}
return selectiveNamespacesPresent;
}
@Override
public ConfigurationCondition.ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
}

View File

@@ -42,6 +42,7 @@ import org.springframework.cloud.kubernetes.commons.discovery.ConditionalOnKuber
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.log.LogAccessor;
@@ -57,6 +58,7 @@ import static org.springframework.cloud.kubernetes.client.KubernetesClientUtils.
@ConditionalOnKubernetesDiscoveryEnabled
@ConditionalOnBlockingOrReactiveEnabled
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
@Conditional(ConditionalOnSelectiveNamespacesMissing.class)
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class })
@AutoConfigureAfter({ KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class })
public class KubernetesClientInformerAutoConfiguration {
@@ -67,6 +69,7 @@ public class KubernetesClientInformerAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SharedInformerFactory sharedInformerFactory(ApiClient client) {
LOG.debug(() -> "registering sharedInformerFactory for non-selective namespaces");
return new SharedInformerFactory(client);
}

View File

@@ -0,0 +1,166 @@
/*
* 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.client.discovery;
import java.util.ArrayList;
import java.util.List;
import io.kubernetes.client.informer.SharedIndexInformer;
import io.kubernetes.client.informer.SharedInformerFactory;
import io.kubernetes.client.informer.cache.Lister;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.models.V1Endpoints;
import io.kubernetes.client.openapi.models.V1EndpointsList;
import io.kubernetes.client.openapi.models.V1Service;
import io.kubernetes.client.openapi.models.V1ServiceList;
import io.kubernetes.client.util.generic.GenericKubernetesApi;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.cloud.client.CommonsClientAutoConfiguration;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration;
import org.springframework.cloud.kubernetes.commons.discovery.ConditionalOnKubernetesDiscoveryEnabled;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.log.LogAccessor;
/**
* Auto-configuration to be used when "spring.cloud.kubernetes.discovery.namespaces" is
* defined.
*
* @author wind57
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnKubernetesDiscoveryEnabled
@ConditionalOnBlockingOrReactiveEnabled
@Conditional(ConditionalOnSelectiveNamespacesPresent.class)
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class })
@AutoConfigureAfter({ KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class })
public class KubernetesClientInformerSelectiveNamespacesAutoConfiguration {
private static final LogAccessor LOG = new LogAccessor(
LogFactory.getLog(KubernetesClientInformerSelectiveNamespacesAutoConfiguration.class));
// we rely on the order of namespaces to enable listers, as such provide a bean of
// namespaces
// as a list, instead of the incoming Set.
@Bean
@ConditionalOnMissingBean
public List<String> selectiveNamespaces(KubernetesDiscoveryProperties properties) {
List<String> selectiveNamespaces = properties.namespaces().stream().sorted().toList();
LOG.debug(() -> "using selective namespaces : " + selectiveNamespaces);
return selectiveNamespaces;
}
@Bean
@ConditionalOnMissingBean(value = SharedInformerFactory.class, parameterizedContainer = List.class)
public List<SharedInformerFactory> sharedInformerFactories(ApiClient apiClient, List<String> selectiveNamespaces) {
int howManyNamespaces = selectiveNamespaces.size();
List<SharedInformerFactory> sharedInformerFactories = new ArrayList<>(howManyNamespaces);
for (int i = 0; i < howManyNamespaces; ++i) {
sharedInformerFactories.add(new SharedInformerFactory(apiClient));
}
return sharedInformerFactories;
}
@Bean
@ConditionalOnMissingBean(value = V1Service.class,
parameterizedContainer = { List.class, SharedIndexInformer.class })
public List<SharedIndexInformer<V1Service>> serviceSharedIndexInformers(
List<SharedInformerFactory> sharedInformerFactories, List<String> selectiveNamespaces,
ApiClient apiClient) {
int howManyNamespaces = selectiveNamespaces.size();
List<SharedIndexInformer<V1Service>> serviceSharedIndexedInformers = new ArrayList<>(howManyNamespaces);
for (int i = 0; i < howManyNamespaces; ++i) {
GenericKubernetesApi<V1Service, V1ServiceList> servicesApi = new GenericKubernetesApi<>(V1Service.class,
V1ServiceList.class, "", "v1", "services", apiClient);
SharedIndexInformer<V1Service> sharedIndexInformer = sharedInformerFactories.get(i)
.sharedIndexInformerFor(servicesApi, V1Service.class, 0L, selectiveNamespaces.get(i));
serviceSharedIndexedInformers.add(sharedIndexInformer);
}
return serviceSharedIndexedInformers;
}
@Bean
@ConditionalOnMissingBean(value = V1Service.class, parameterizedContainer = { List.class, Lister.class })
public List<Lister<V1Service>> serviceListers(List<String> selectiveNamespaces,
List<SharedIndexInformer<V1Service>> serviceSharedIndexInformers) {
int howManyNamespaces = selectiveNamespaces.size();
List<Lister<V1Service>> serviceListers = new ArrayList<>(howManyNamespaces);
for (int i = 0; i < howManyNamespaces; ++i) {
String namespace = selectiveNamespaces.get(i);
Lister<V1Service> lister = new Lister<>(serviceSharedIndexInformers.get(i).getIndexer(), namespace);
LOG.debug(() -> "registering lister (for services) in namespace : " + namespace);
serviceListers.add(lister);
}
return serviceListers;
}
@Bean
@ConditionalOnMissingBean(value = V1Endpoints.class,
parameterizedContainer = { List.class, SharedIndexInformer.class })
public List<SharedIndexInformer<V1Endpoints>> endpointsSharedIndexInformers(
List<SharedInformerFactory> sharedInformerFactories, List<String> selectiveNamespaces,
ApiClient apiClient) {
int howManyNamespaces = selectiveNamespaces.size();
List<SharedIndexInformer<V1Endpoints>> endpointsSharedIndexedInformers = new ArrayList<>(howManyNamespaces);
for (int i = 0; i < howManyNamespaces; ++i) {
GenericKubernetesApi<V1Endpoints, V1EndpointsList> endpointsApi = new GenericKubernetesApi<>(
V1Endpoints.class, V1EndpointsList.class, "", "v1", "endpoints", apiClient);
SharedIndexInformer<V1Endpoints> sharedIndexInformer = sharedInformerFactories.get(i)
.sharedIndexInformerFor(endpointsApi, V1Endpoints.class, 0L, selectiveNamespaces.get(i));
endpointsSharedIndexedInformers.add(sharedIndexInformer);
}
return endpointsSharedIndexedInformers;
}
@Bean
@ConditionalOnMissingBean(value = V1Endpoints.class, parameterizedContainer = { List.class, Lister.class })
public List<Lister<V1Endpoints>> endpointsListers(List<String> selectiveNamespaces,
List<SharedIndexInformer<V1Endpoints>> serviceSharedIndexInformers) {
int howManyNamespaces = selectiveNamespaces.size();
List<Lister<V1Endpoints>> endpointsListers = new ArrayList<>(howManyNamespaces);
for (int i = 0; i < howManyNamespaces; ++i) {
String namespace = selectiveNamespaces.get(i);
Lister<V1Endpoints> lister = new Lister<>(serviceSharedIndexInformers.get(i).getIndexer());
LOG.debug(() -> "registering lister (for endpoints) in namespace : " + namespace);
endpointsListers.add(lister);
}
return endpointsListers;
}
}

View File

@@ -41,6 +41,7 @@ import org.springframework.cloud.kubernetes.commons.discovery.ConditionalOnKuber
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.log.LogAccessor;
@@ -59,6 +60,7 @@ import static io.kubernetes.client.util.Namespaces.NAMESPACE_DEFAULT;
@ConditionalOnKubernetesDiscoveryEnabled
@ConditionalOnBlockingOrReactiveEnabled
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
@Conditional(ConditionalOnSelectiveNamespacesMissing.class)
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class })
@AutoConfigureAfter({ KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class })
public class KubernetesInformerAutoConfiguration {

View File

@@ -63,14 +63,14 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(KubernetesInformerDiscoveryClient.class));
private final SharedInformerFactory sharedInformerFactory;
private final List<SharedInformerFactory> sharedInformerFactories;
private final Lister<V1Service> serviceLister;
private final List<Lister<V1Service>> serviceListers;
private final List<Lister<V1Endpoints>> endpointsListers;
private final Supplier<Boolean> informersReadyFunc;
private final Lister<V1Endpoints> endpointsLister;
private final KubernetesDiscoveryProperties properties;
@Deprecated(forRemoval = true)
@@ -78,9 +78,9 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
Lister<V1Service> serviceLister, Lister<V1Endpoints> endpointsLister,
SharedInformer<V1Service> serviceInformer, SharedInformer<V1Endpoints> endpointsInformer,
KubernetesDiscoveryProperties properties) {
this.sharedInformerFactory = sharedInformerFactory;
this.serviceLister = serviceLister;
this.endpointsLister = endpointsLister;
this.sharedInformerFactories = List.of(sharedInformerFactory);
this.serviceListers = List.of(serviceLister);
this.endpointsListers = List.of(endpointsLister);
this.informersReadyFunc = () -> serviceInformer.hasSynced() && endpointsInformer.hasSynced();
this.properties = properties;
}
@@ -89,13 +89,32 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
Lister<V1Service> serviceLister, Lister<V1Endpoints> endpointsLister,
SharedInformer<V1Service> serviceInformer, SharedInformer<V1Endpoints> endpointsInformer,
KubernetesDiscoveryProperties properties) {
this.sharedInformerFactory = sharedInformerFactory;
this.serviceLister = serviceLister;
this.endpointsLister = endpointsLister;
this.sharedInformerFactories = List.of(sharedInformerFactory);
this.serviceListers = List.of(serviceLister);
this.endpointsListers = List.of(endpointsLister);
this.informersReadyFunc = () -> serviceInformer.hasSynced() && endpointsInformer.hasSynced();
this.properties = properties;
}
public KubernetesInformerDiscoveryClient(List<SharedInformerFactory> sharedInformerFactories,
List<Lister<V1Service>> serviceListers, List<Lister<V1Endpoints>> endpointsListers,
List<SharedInformer<V1Service>> serviceInformers, List<SharedInformer<V1Endpoints>> endpointsInformers,
KubernetesDiscoveryProperties properties) {
this.sharedInformerFactories = sharedInformerFactories;
this.serviceListers = serviceListers;
this.endpointsListers = endpointsListers;
this.informersReadyFunc = () -> {
boolean serviceInformersReady = serviceInformers.isEmpty() || serviceInformers.stream()
.map(SharedInformer::hasSynced).reduce(Boolean::logicalAnd).orElse(false);
boolean endpointsInformersReady = endpointsInformers.isEmpty() || endpointsInformers.stream()
.map(SharedInformer::hasSynced).reduce(Boolean::logicalAnd).orElse(false);
return serviceInformersReady && endpointsInformersReady;
};
this.properties = properties;
}
@Override
public String description() {
return "Kubernetes Client Discovery";
@@ -105,11 +124,11 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
public List<ServiceInstance> getInstances(String serviceId) {
Objects.requireNonNull(serviceId, "serviceId must be provided");
List<V1Service> services = serviceLister.list().stream()
.filter(svc -> serviceId.equals(svc.getMetadata().getName())).toList();
if (services.size() == 0 || !services.stream().anyMatch(service -> matchesServiceLabels(service, properties))) {
// no such service present in the cluster
return new ArrayList<>();
List<V1Service> services = serviceListers.stream().flatMap(x -> x.list().stream())
.filter(scv -> scv.getMetadata() != null).filter(svc -> serviceId.equals(svc.getMetadata().getName()))
.toList();
if (services.size() == 0 || services.stream().noneMatch(service -> matchesServiceLabels(service, properties))) {
return List.of();
}
return services.stream().flatMap(s -> getServiceInstanceDetails(s, serviceId)).toList();
}
@@ -117,12 +136,10 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
private Stream<ServiceInstance> getServiceInstanceDetails(V1Service service, String serviceId) {
Map<String, String> serviceMetadata = serviceMetadata(properties, service, serviceId);
V1Endpoints ep = endpointsLister.namespace(service.getMetadata().getNamespace())
.get(service.getMetadata().getName());
if (ep == null || ep.getSubsets() == null) {
// no available endpoints in the cluster
return Stream.empty();
}
List<V1Endpoints> endpoints = endpointsListers.stream()
.map(endpointsLister -> endpointsLister.namespace(service.getMetadata().getNamespace())
.get(service.getMetadata().getName()))
.filter(Objects::nonNull).filter(ep -> ep.getSubsets() != null).toList();
Optional<String> discoveredPrimaryPortName = Optional.empty();
if (service.getMetadata() != null && service.getMetadata().getLabels() != null) {
@@ -133,33 +150,36 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
final boolean secured = isSecured(service);
return ep.getSubsets().stream().filter(subset -> subset.getPorts() != null && subset.getPorts().size() > 0) // safeguard
.flatMap(subset -> {
Map<String, String> metadata = new HashMap<>(serviceMetadata);
List<CoreV1EndpointPort> endpointPorts = subset.getPorts();
if (properties.metadata() != null && properties.metadata().addPorts()) {
endpointPorts.forEach(
p -> metadata.put(StringUtils.hasText(p.getName()) ? p.getName() : UNSET_PORT_NAME,
return endpoints.stream()
.flatMap(ep -> ep.getSubsets().stream()
.filter(subset -> subset.getPorts() != null && subset.getPorts().size() > 0) // safeguard
.flatMap(subset -> {
Map<String, String> metadata = new HashMap<>(serviceMetadata);
List<CoreV1EndpointPort> endpointPorts = subset.getPorts();
if (properties.metadata() != null && properties.metadata().addPorts()) {
endpointPorts.forEach(p -> metadata.put(
StringUtils.hasText(p.getName()) ? p.getName() : UNSET_PORT_NAME,
Integer.toString(p.getPort())));
}
List<V1EndpointAddress> addresses = subset.getAddresses();
if (addresses == null) {
addresses = new ArrayList<>();
}
if (properties.includeNotReadyAddresses()
&& !CollectionUtils.isEmpty(subset.getNotReadyAddresses())) {
addresses.addAll(subset.getNotReadyAddresses());
}
}
List<V1EndpointAddress> addresses = subset.getAddresses();
if (addresses == null) {
addresses = new ArrayList<>();
}
if (properties.includeNotReadyAddresses()
&& !CollectionUtils.isEmpty(subset.getNotReadyAddresses())) {
addresses.addAll(subset.getNotReadyAddresses());
}
final int port = findEndpointPort(endpointPorts, primaryPortName, serviceId);
return addresses.stream()
.map(addr -> new DefaultKubernetesServiceInstance(
addr.getTargetRef() != null ? addr.getTargetRef().getUid() : "", serviceId,
addr.getIp(), port, metadata, secured, service.getMetadata().getNamespace(),
// TODO find out how to get cluster name possibly from
// KubeConfig
null));
});
final int port = findEndpointPort(endpointPorts, primaryPortName, serviceId);
return addresses.stream()
.map(addr -> new DefaultKubernetesServiceInstance(
addr.getTargetRef() != null ? addr.getTargetRef().getUid() : "", serviceId,
addr.getIp(), port, metadata, secured, service.getMetadata().getNamespace(),
// TODO find out how to get cluster name
// possibly from
// KubeConfig
null));
}));
}
private static boolean isSecured(V1Service service) {
@@ -209,17 +229,16 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
@Override
public List<String> getServices() {
List<String> services = serviceLister.list().stream()
List<String> services = serviceListers.stream().flatMap(serviceLister -> serviceLister.list().stream())
.filter(service -> matchesServiceLabels(service, properties)).map(s -> s.getMetadata().getName())
.distinct().toList();
LOG.debug(() -> "will return services : " + services);
return services;
}
@PostConstruct
public void afterPropertiesSet() {
sharedInformerFactory.startAllRegisteredInformers();
sharedInformerFactories.forEach(SharedInformerFactory::startAllRegisteredInformers);
if (!Wait.poll(Duration.ofSeconds(1), Duration.ofSeconds(properties.cacheLoadingTimeoutSeconds()), () -> {
LOG.info(() -> "Waiting for the cache of informers to be fully loaded..");
return informersReadyFunc.get();
@@ -233,8 +252,8 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
() -> "Timeout waiting for informers cache to be ready, ignoring the failure because waitForInformerCacheReady property is false");
}
}
LOG.info(() -> "Cache fully loaded (total " + serviceLister.list().size()
+ " services) , discovery client is now available");
LOG.info(() -> "Cache fully loaded (total " + serviceListers.stream().mapToLong(x -> x.list().size()).sum()
+ " services), discovery client is now available");
}
@Override

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.kubernetes.client.discovery;
import java.util.List;
import io.kubernetes.client.informer.SharedInformer;
import io.kubernetes.client.informer.SharedInformerFactory;
import io.kubernetes.client.informer.cache.Lister;
@@ -44,6 +46,7 @@ import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscover
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.log.LogAccessor;
@@ -57,7 +60,8 @@ import org.springframework.core.log.LogAccessor;
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class })
@AutoConfigureAfter({ KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class,
KubernetesClientInformerAutoConfiguration.class })
KubernetesClientInformerAutoConfiguration.class,
KubernetesClientInformerSelectiveNamespacesAutoConfiguration.class })
public class KubernetesInformerDiscoveryClientAutoConfiguration {
private static final LogAccessor LOG = new LogAccessor(
@@ -90,7 +94,8 @@ public class KubernetesInformerDiscoveryClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public KubernetesInformerDiscoveryClient kubernetesClientInformerDiscoveryClient(
@Conditional(ConditionalOnSelectiveNamespacesMissing.class)
KubernetesInformerDiscoveryClient kubernetesClientInformerDiscoveryClient(
SharedInformerFactory sharedInformerFactory, Lister<V1Service> serviceLister,
Lister<V1Endpoints> endpointsLister, SharedInformer<V1Service> serviceInformer,
SharedInformer<V1Endpoints> endpointsInformer, KubernetesDiscoveryProperties properties) {
@@ -98,4 +103,15 @@ public class KubernetesInformerDiscoveryClientAutoConfiguration {
serviceInformer, endpointsInformer, properties);
}
@Bean
@ConditionalOnMissingBean
@Conditional(ConditionalOnSelectiveNamespacesPresent.class)
KubernetesInformerDiscoveryClient selectiveNamespacesKubernetesInformerDiscoveryClient(
List<SharedInformerFactory> sharedInformerFactories, List<Lister<V1Service>> serviceListers,
List<Lister<V1Endpoints>> endpointsListers, List<SharedInformer<V1Service>> serviceInformers,
List<SharedInformer<V1Endpoints>> endpointsInformers, KubernetesDiscoveryProperties properties) {
return new KubernetesInformerDiscoveryClient(sharedInformerFactories, serviceListers, endpointsListers,
serviceInformers, endpointsInformers, properties);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 the original author or authors.
* Copyright 2019-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.
@@ -49,6 +49,8 @@ public class KubernetesInformerReactiveDiscoveryClient implements ReactiveDiscov
serviceInformer, endpointsInformer, properties);
}
// this is either kubernetesClientInformerDiscoveryClient
// or selectiveNamespacesKubernetesClientInformerDiscoveryClient
KubernetesInformerReactiveDiscoveryClient(KubernetesInformerDiscoveryClient kubernetesDiscoveryClient) {
this.kubernetesDiscoveryClient = kubernetesDiscoveryClient;
}

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.kubernetes.client.discovery.reactive;
import java.util.List;
import io.kubernetes.client.informer.SharedInformer;
import io.kubernetes.client.informer.SharedInformerFactory;
import io.kubernetes.client.informer.cache.Lister;
@@ -39,7 +41,10 @@ import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIn
import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryClientHealthIndicator;
import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration;
import org.springframework.cloud.kubernetes.client.KubernetesClientPodUtils;
import org.springframework.cloud.kubernetes.client.discovery.ConditionalOnSelectiveNamespacesMissing;
import org.springframework.cloud.kubernetes.client.discovery.ConditionalOnSelectiveNamespacesPresent;
import org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerAutoConfiguration;
import org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerSelectiveNamespacesAutoConfiguration;
import org.springframework.cloud.kubernetes.client.discovery.KubernetesInformerDiscoveryClient;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.commons.PodUtils;
@@ -49,6 +54,7 @@ import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscover
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.log.LogAccessor;
@@ -66,7 +72,8 @@ import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesD
@AutoConfigureBefore({ SimpleReactiveDiscoveryClientAutoConfiguration.class,
ReactiveCommonsClientAutoConfiguration.class })
@AutoConfigureAfter({ ReactiveCompositeDiscoveryClientAutoConfiguration.class,
KubernetesDiscoveryPropertiesAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class })
KubernetesDiscoveryPropertiesAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class,
KubernetesClientInformerSelectiveNamespacesAutoConfiguration.class })
public class KubernetesInformerReactiveDiscoveryClientAutoConfiguration {
private static final LogAccessor LOG = new LogAccessor(
@@ -127,6 +134,7 @@ public class KubernetesInformerReactiveDiscoveryClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(ConditionalOnSelectiveNamespacesMissing.class)
KubernetesInformerDiscoveryClient kubernetesClientInformerDiscoveryClient(
SharedInformerFactory sharedInformerFactory, Lister<V1Service> serviceLister,
Lister<V1Endpoints> endpointsLister, SharedInformer<V1Service> serviceInformer,
@@ -135,4 +143,15 @@ public class KubernetesInformerReactiveDiscoveryClientAutoConfiguration {
serviceInformer, endpointsInformer, properties);
}
@Bean
@ConditionalOnMissingBean
@Conditional(ConditionalOnSelectiveNamespacesPresent.class)
KubernetesInformerDiscoveryClient selectiveNamespacesKubernetesClientInformerDiscoveryClient(
List<SharedInformerFactory> sharedInformerFactories, List<Lister<V1Service>> serviceListers,
List<Lister<V1Endpoints>> endpointsListers, List<SharedInformer<V1Service>> serviceInformers,
List<SharedInformer<V1Endpoints>> endpointsInformers, KubernetesDiscoveryProperties properties) {
return new KubernetesInformerDiscoveryClient(sharedInformerFactories, serviceListers, endpointsListers,
serviceInformers, endpointsInformers, properties);
}
}

View File

@@ -2,3 +2,4 @@ org.springframework.cloud.kubernetes.client.discovery.catalog.KubernetesCatalogW
org.springframework.cloud.kubernetes.client.discovery.KubernetesInformerDiscoveryClientAutoConfiguration
org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerAutoConfiguration
org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClientAutoConfiguration
org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerSelectiveNamespacesAutoConfiguration

View File

@@ -0,0 +1,62 @@
/*
* 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.client.discovery;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.mock.env.MockEnvironment;
/**
* @author wind57
*/
public class ConditionalOnSelectiveNamespacesDisabledTests {
private static final ConditionalOnSelectiveNamespacesMissing TO_TEST = new ConditionalOnSelectiveNamespacesMissing();
private static final ConditionContext CONDITION_CONTEXT = Mockito.mock(ConditionContext.class);
@Test
void testSelectiveNamespacesNotPresent() {
MockEnvironment environment = new MockEnvironment();
Mockito.when(CONDITION_CONTEXT.getEnvironment()).thenReturn(environment);
boolean result = TO_TEST.matches(CONDITION_CONTEXT, null);
Assertions.assertTrue(result);
}
@Test
void testSelectiveNamespacesPresentEmpty() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.cloud.kubernetes.discovery.namespaces", "");
Mockito.when(CONDITION_CONTEXT.getEnvironment()).thenReturn(environment);
boolean result = TO_TEST.matches(CONDITION_CONTEXT, null);
Assertions.assertTrue(result);
}
@Test
void testSelectiveNamespacesPresentNonEmpty() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.cloud.kubernetes.discovery.namespaces", "default");
Mockito.when(CONDITION_CONTEXT.getEnvironment()).thenReturn(environment);
boolean result = TO_TEST.matches(CONDITION_CONTEXT, null);
Assertions.assertFalse(result);
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.client.discovery;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.mock.env.MockEnvironment;
/**
* @author wind57
*/
class ConditionalOnSelectiveNamespacesEnabledTests {
private static final ConditionalOnSelectiveNamespacesPresent TO_TEST = new ConditionalOnSelectiveNamespacesPresent();
private static final ConditionContext CONDITION_CONTEXT = Mockito.mock(ConditionContext.class);
@Test
void testSelectiveNamespacesNotPresent() {
MockEnvironment environment = new MockEnvironment();
Mockito.when(CONDITION_CONTEXT.getEnvironment()).thenReturn(environment);
boolean result = TO_TEST.matches(CONDITION_CONTEXT, null);
Assertions.assertFalse(result);
}
@Test
void testSelectiveNamespacesPresentEmpty() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.cloud.kubernetes.discovery.namespaces", "");
Mockito.when(CONDITION_CONTEXT.getEnvironment()).thenReturn(environment);
boolean result = TO_TEST.matches(CONDITION_CONTEXT, null);
Assertions.assertFalse(result);
}
@Test
void testSelectiveNamespacesPresentNonEmpty() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.cloud.kubernetes.discovery.namespaces", "default");
Mockito.when(CONDITION_CONTEXT.getEnvironment()).thenReturn(environment);
boolean result = TO_TEST.matches(CONDITION_CONTEXT, null);
Assertions.assertTrue(result);
}
}

View File

@@ -18,9 +18,6 @@ package org.springframework.cloud.kubernetes.client.discovery;
import java.io.StringReader;
import io.kubernetes.client.informer.SharedIndexInformer;
import io.kubernetes.client.informer.SharedInformerFactory;
import io.kubernetes.client.informer.cache.Lister;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.util.Config;
import org.junit.jupiter.api.AfterAll;
@@ -31,7 +28,9 @@ import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryClientHealthIndicator;
import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration;
import org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClient;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryClientHealthIndicatorInitializer;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration;
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
@@ -40,6 +39,10 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.kubernetes.client.discovery.TestUtils.assertNonSelectiveNamespacesBeansMissing;
import static org.springframework.cloud.kubernetes.client.discovery.TestUtils.assertNonSelectiveNamespacesBeansPresent;
import static org.springframework.cloud.kubernetes.client.discovery.TestUtils.assertSelectiveNamespacesBeansMissing;
import static org.springframework.cloud.kubernetes.client.discovery.TestUtils.assertSelectiveNamespacesBeansPresent;
/**
* Test various conditionals for
@@ -62,11 +65,30 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
void discoveryEnabledDefault() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void discoveryEnabledDefaultWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.kubernetes.discovery.namespaces=a,b,c");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 3);
});
}
@@ -75,11 +97,30 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.enabled=true");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void discoveryEnabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.enabled=true", "spring.cloud.kubernetes.discovery.namespaces=a,b");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 2);
});
}
@@ -88,11 +129,30 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(SharedInformerFactory.class);
assertThat(context).doesNotHaveBean(SharedIndexInformer.class);
assertThat(context).doesNotHaveBean(Lister.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void discoveryDisabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@@ -101,11 +161,31 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.kubernetes.discovery.enabled=true");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void kubernetesDiscoveryEnabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.kubernetes.discovery.enabled=true",
"spring.cloud.kubernetes.discovery.namespaces=a,b,c,d");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 4);
});
}
@@ -114,11 +194,31 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.kubernetes.discovery.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(SharedInformerFactory.class);
assertThat(context).doesNotHaveBean(SharedIndexInformer.class);
assertThat(context).doesNotHaveBean(Lister.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void kubernetesDiscoveryDisabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.kubernetes.discovery.enabled=false",
"spring.cloud.kubernetes.discovery.namespaces=a,b,c,d");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@@ -127,11 +227,30 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.blocking.enabled=true");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void kubernetesDiscoveryBlockingEnabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.blocking.enabled=true", "spring.cloud.kubernetes.discovery.namespaces=a");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 1);
});
}
@@ -140,11 +259,30 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.blocking.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void kubernetesDiscoveryBlockingDisabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.blocking.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 2);
});
}
@@ -153,11 +291,31 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.client.health-indicator.enabled=true");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void kubernetesDiscoveryHealthIndicatorEnabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.client.health-indicator.enabled=true",
"spring.cloud.kubernetes.discovery.namespaces=b");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 1);
});
}
@@ -166,11 +324,31 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.client.health-indicator.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void kubernetesDiscoveryHealthIndicatorDisabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.client.health-indicator.enabled=false",
"spring.cloud.kubernetes.discovery.namespaces=b");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 1);
});
}
@@ -179,11 +357,31 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
setupWithFilteredClassLoader(HealthIndicator.class, "spring.main.cloud-platform=KUBERNETES",
"spring.cloud.config.enabled=false", "spring.cloud.discovery.client.health-indicator.enabled=true");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void kubernetesDiscoveryHealthIndicatorEnabledHealthIndicatorMissingWithSelectiveNamespaces() {
setupWithFilteredClassLoader(HealthIndicator.class, "spring.main.cloud-platform=KUBERNETES",
"spring.cloud.config.enabled=false", "spring.cloud.discovery.client.health-indicator.enabled=true",
"spring.cloud.kubernetes.discovery.namespaces=a,b,c,d,e");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 5);
});
}
@@ -195,11 +393,34 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.reactive.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
/**
* reactive is disabled and should not impact blocking in any way
*/
@Test
void reactiveDisabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.reactive.enabled=false",
"spring.cloud.kubernetes.discovery.namespaces=a,b,c,d,e");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 5);
});
}
@@ -207,7 +428,8 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
applicationContextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(KubernetesInformerDiscoveryClientAutoConfiguration.class,
KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class,
KubernetesClientInformerAutoConfiguration.class))
KubernetesClientInformerAutoConfiguration.class,
KubernetesClientInformerSelectiveNamespacesAutoConfiguration.class))
.withUserConfiguration(ApiClientConfig.class).withPropertyValues(properties);
}
@@ -215,7 +437,8 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
applicationContextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(KubernetesInformerDiscoveryClientAutoConfiguration.class,
KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class,
KubernetesClientInformerAutoConfiguration.class))
KubernetesClientInformerAutoConfiguration.class,
KubernetesClientInformerSelectiveNamespacesAutoConfiguration.class))
.withClassLoader(new FilteredClassLoader(cls)).withUserConfiguration(ApiClientConfig.class)
.withPropertyValues(properties);
}

View File

@@ -95,9 +95,11 @@ class KubernetesInformerDiscoveryClientTests {
@Test
void testDiscoveryGetServicesAllNamespaceShouldWork() {
Lister<V1Service> serviceLister = setupServiceLister(SERVICE_1, SERVICE_2);
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(ENDPOINTS_NO_UNSET_PORT_NAME);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(
SHARED_INFORMER_FACTORY, serviceLister, null, null, null, KubernetesDiscoveryProperties.DEFAULT);
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null,
KubernetesDiscoveryProperties.DEFAULT);
assertThat(discoveryClient.getServices().toArray()).containsOnly(SERVICE_1.getMetadata().getName(),
SERVICE_2.getMetadata().getName());
@@ -107,12 +109,13 @@ class KubernetesInformerDiscoveryClientTests {
@Test
void testDiscoveryWithServiceLabels() {
Lister<V1Service> serviceLister = setupServiceLister(SERVICE_1, SERVICE_2, SERVICE_3);
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(ENDPOINTS_NO_UNSET_PORT_NAME);
Map<String, String> labels = Map.of("k8s", "true", "spring", "true");
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = properties(true, labels);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(
SHARED_INFORMER_FACTORY, serviceLister, null, null, null, kubernetesDiscoveryProperties);
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
assertThat(discoveryClient.getServices().toArray()).containsOnly(SERVICE_3.getMetadata().getName());
@@ -164,9 +167,11 @@ class KubernetesInformerDiscoveryClientTests {
@Test
void testDiscoveryGetServicesOneNamespaceShouldWork() {
Lister<V1Service> serviceLister = setupServiceLister(SERVICE_1, SERVICE_2);
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(ENDPOINTS_NO_UNSET_PORT_NAME);
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(
SHARED_INFORMER_FACTORY, serviceLister, null, null, null, KubernetesDiscoveryProperties.DEFAULT);
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null,
KubernetesDiscoveryProperties.DEFAULT);
assertThat(discoveryClient.getServices().toArray()).containsOnly(SERVICE_1.getMetadata().getName());

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.client.discovery;
import java.util.List;
import io.kubernetes.client.informer.SharedIndexInformer;
import io.kubernetes.client.informer.SharedInformerFactory;
import io.kubernetes.client.informer.cache.Lister;
import io.kubernetes.client.openapi.models.V1Endpoints;
import io.kubernetes.client.openapi.models.V1Service;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Some common class that test code delegates to.
*
* @author wind57
*/
public final class TestUtils {
private TestUtils() {
}
public static void assertSelectiveNamespacesBeansMissing(AssertableApplicationContext context) {
String[] sharedInformerFactoriesBeanName = context.getBeanNamesForType(
ResolvableType.forType(new ParameterizedTypeReference<List<SharedInformerFactory>>() {
}));
assertThat(sharedInformerFactoriesBeanName).isEmpty();
String[] serviceSharedIndexInformersBeanName = context.getBeanNamesForType(
ResolvableType.forType(new ParameterizedTypeReference<List<SharedIndexInformer<V1Service>>>() {
}));
assertThat(serviceSharedIndexInformersBeanName).isEmpty();
String[] endpointsSharedIndexInformersBeanName = context.getBeanNamesForType(
ResolvableType.forType(new ParameterizedTypeReference<List<SharedIndexInformer<V1Endpoints>>>() {
}));
assertThat(endpointsSharedIndexInformersBeanName).isEmpty();
String[] serviceListersBeanName = context
.getBeanNamesForType(ResolvableType.forType(new ParameterizedTypeReference<List<Lister<V1Service>>>() {
}));
assertThat(serviceListersBeanName).isEmpty();
String[] endpointsListersBeanName = context.getBeanNamesForType(
ResolvableType.forType(new ParameterizedTypeReference<List<Lister<V1Endpoints>>>() {
}));
assertThat(endpointsListersBeanName).isEmpty();
}
@SuppressWarnings("unchecked")
public static void assertSelectiveNamespacesBeansPresent(AssertableApplicationContext context, int times) {
String sharedInformerFactoriesBeanName = context.getBeanNamesForType(
ResolvableType.forType(new ParameterizedTypeReference<List<SharedInformerFactory>>() {
}))[0];
List<SharedInformerFactory> sharedInformerFactories = (List<SharedInformerFactory>) context
.getBean(sharedInformerFactoriesBeanName);
assertThat(sharedInformerFactories.size()).isEqualTo(times);
String serviceSharedIndexInformersBeanName = context.getBeanNamesForType(
ResolvableType.forType(new ParameterizedTypeReference<List<SharedIndexInformer<V1Service>>>() {
}))[0];
List<SharedIndexInformer<V1Service>> serviceSharedIndexInformers = (List<SharedIndexInformer<V1Service>>) context
.getBean(serviceSharedIndexInformersBeanName);
assertThat(serviceSharedIndexInformers.size()).isEqualTo(times);
String endpointsSharedIndexInformersBeanName = context.getBeanNamesForType(
ResolvableType.forType(new ParameterizedTypeReference<List<SharedIndexInformer<V1Endpoints>>>() {
}))[0];
List<SharedIndexInformer<V1Endpoints>> endpointsSharedIndexInformers = (List<SharedIndexInformer<V1Endpoints>>) context
.getBean(endpointsSharedIndexInformersBeanName);
assertThat(endpointsSharedIndexInformers.size()).isEqualTo(times);
String serviceListersBeanName = context
.getBeanNamesForType(ResolvableType.forType(new ParameterizedTypeReference<List<Lister<V1Service>>>() {
}))[0];
List<Lister<V1Service>> serviceListers = (List<Lister<V1Service>>) context.getBean(serviceListersBeanName);
assertThat(serviceListers.size()).isEqualTo(times);
String endpointsListersBeanName = context.getBeanNamesForType(
ResolvableType.forType(new ParameterizedTypeReference<List<Lister<V1Endpoints>>>() {
}))[0];
List<Lister<V1Endpoints>> endpointsListers = (List<Lister<V1Endpoints>>) context
.getBean(endpointsListersBeanName);
assertThat(endpointsListers.size()).isEqualTo(times);
}
@SuppressWarnings("unchecked")
public static void assertNonSelectiveNamespacesBeansPresent(AssertableApplicationContext context) {
assertThat(context).hasSingleBean(SharedInformerFactory.class);
String serviceSharedIndexInformerBeanName = context.getBeanNamesForType(
ResolvableType.forType(new ParameterizedTypeReference<SharedIndexInformer<V1Service>>() {
}))[0];
SharedIndexInformer<V1Service> serviceSharedIndexInformer = (SharedIndexInformer<V1Service>) context
.getBean(serviceSharedIndexInformerBeanName);
assertThat(serviceSharedIndexInformer).isNotNull();
String endpointSharedIndexInformerBeanName = context.getBeanNamesForType(
ResolvableType.forType(new ParameterizedTypeReference<SharedIndexInformer<V1Endpoints>>() {
}))[0];
SharedIndexInformer<V1Endpoints> endpointsSharedIndexInformer = (SharedIndexInformer<V1Endpoints>) context
.getBean(endpointSharedIndexInformerBeanName);
assertThat(endpointsSharedIndexInformer).isNotNull();
String serviceListerBeanName = context
.getBeanNamesForType(ResolvableType.forType(new ParameterizedTypeReference<Lister<V1Service>>() {
}))[0];
Lister<V1Service> serviceLister = (Lister<V1Service>) context.getBean(serviceListerBeanName);
assertThat(serviceLister).isNotNull();
String endpointsListerBeanName = context
.getBeanNamesForType(ResolvableType.forType(new ParameterizedTypeReference<Lister<V1Endpoints>>() {
}))[0];
Lister<V1Endpoints> endpointsLister = (Lister<V1Endpoints>) context.getBean(endpointsListerBeanName);
assertThat(endpointsLister).isNotNull();
}
public static void assertNonSelectiveNamespacesBeansMissing(AssertableApplicationContext context) {
String[] serviceSharedIndexInformerBeanName = context.getBeanNamesForType(
ResolvableType.forType(new ParameterizedTypeReference<SharedIndexInformer<V1Service>>() {
}));
assertThat(serviceSharedIndexInformerBeanName).isEmpty();
String[] endpointSharedIndexInformerBeanName = context.getBeanNamesForType(
ResolvableType.forType(new ParameterizedTypeReference<SharedIndexInformer<V1Endpoints>>() {
}));
assertThat(endpointSharedIndexInformerBeanName).isEmpty();
String[] serviceListerBeanName = context
.getBeanNamesForType(ResolvableType.forType(new ParameterizedTypeReference<Lister<V1Service>>() {
}));
assertThat(serviceListerBeanName).isEmpty();
String[] endpointsListerBeanName = context
.getBeanNamesForType(ResolvableType.forType(new ParameterizedTypeReference<Lister<V1Endpoints>>() {
}));
assertThat(endpointsListerBeanName).isEmpty();
}
}

View File

@@ -18,9 +18,6 @@ package org.springframework.cloud.kubernetes.client.discovery.reactive;
import java.io.StringReader;
import io.kubernetes.client.informer.SharedIndexInformer;
import io.kubernetes.client.informer.SharedInformerFactory;
import io.kubernetes.client.informer.cache.Lister;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.util.Config;
import org.junit.jupiter.api.AfterAll;
@@ -35,7 +32,10 @@ import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactive
import org.springframework.cloud.commons.util.UtilAutoConfiguration;
import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration;
import org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerAutoConfiguration;
import org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerSelectiveNamespacesAutoConfiguration;
import org.springframework.cloud.kubernetes.client.discovery.KubernetesInformerDiscoveryClient;
import org.springframework.cloud.kubernetes.commons.KubernetesCommonsAutoConfiguration;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryClientHealthIndicatorInitializer;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration;
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
import org.springframework.context.annotation.Bean;
@@ -43,6 +43,10 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.kubernetes.client.discovery.TestUtils.assertNonSelectiveNamespacesBeansMissing;
import static org.springframework.cloud.kubernetes.client.discovery.TestUtils.assertNonSelectiveNamespacesBeansPresent;
import static org.springframework.cloud.kubernetes.client.discovery.TestUtils.assertSelectiveNamespacesBeansMissing;
import static org.springframework.cloud.kubernetes.client.discovery.TestUtils.assertSelectiveNamespacesBeansPresent;
/**
* Test various conditionals for
@@ -65,12 +69,36 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
void discoveryEnabledDefault() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
// simple from commons and ours
assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasBean("reactiveIndicatorInitializer");
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void discoveryEnabledDefaultWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.kubernetes.discovery.namespaces=a,b,c");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
// simple from commons and ours
assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasBean("reactiveIndicatorInitializer");
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 3);
});
}
@@ -79,12 +107,36 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.enabled=true");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
// simple from commons and ours
assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasBean("reactiveIndicatorInitializer");
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void discoveryEnabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.enabled=true", "spring.cloud.kubernetes.discovery.namespaces=a,b,c");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
// simple from commons and ours
assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasBean("reactiveIndicatorInitializer");
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 3);
});
}
@@ -93,11 +145,30 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(SharedInformerFactory.class);
assertThat(context).doesNotHaveBean(SharedIndexInformer.class);
assertThat(context).doesNotHaveBean(Lister.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void discoveryDisabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b,c");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@@ -106,12 +177,36 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.kubernetes.discovery.enabled=true");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
// simple from commons and ours
assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasBean("reactiveIndicatorInitializer");
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void kubernetesDiscoveryEnabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.kubernetes.discovery.enabled=true", "spring.cloud.kubernetes.discovery.namespaces=a,b");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
// simple from commons and ours
assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasBean("reactiveIndicatorInitializer");
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 2);
});
}
@@ -120,12 +215,32 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.kubernetes.discovery.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
// only "simple" one from commons, as ours is not picked up
assertThat(context).hasSingleBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void kubernetesDiscoveryDisabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.kubernetes.discovery.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(SharedInformerFactory.class);
assertThat(context).doesNotHaveBean(SharedIndexInformer.class);
assertThat(context).doesNotHaveBean(Lister.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
// only "simple" one from commons, as ours is not picked up
assertThat(context).hasSingleBean(ReactiveDiscoveryClientHealthIndicator.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@@ -134,12 +249,36 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.reactive.enabled=true");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
// simple from commons and ours
assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasBean("reactiveIndicatorInitializer");
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void kubernetesReactiveDiscoveryEnabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.reactive.enabled=true", "spring.cloud.kubernetes.discovery.namespaces=a,b");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
// simple from commons and ours
assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasBean("reactiveIndicatorInitializer");
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 2);
});
}
@@ -148,11 +287,30 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.reactive.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void kubernetesReactiveDiscoveryDisabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.reactive.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).doesNotHaveBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 2);
});
}
@@ -164,12 +322,39 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.blocking.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
// simple from commons and ours
assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasBean("reactiveIndicatorInitializer");
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
/**
* blocking is disabled, and it should not impact reactive in any way.
*/
@Test
void blockingDisabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.blocking.enabled=false", "spring.cloud.kubernetes.discovery.namespaces=a,b");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
// simple from commons and ours
assertThat(context).getBeans(ReactiveDiscoveryClientHealthIndicator.class).size().isEqualTo(2);
assertThat(context).hasSingleBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertThat(context).hasBean("reactiveIndicatorInitializer");
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 2);
});
}
@@ -178,11 +363,33 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.client.health-indicator.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void healthDisabledWithSelectiveNamespaces() {
setup("spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.client.health-indicator.enabled=false",
"spring.cloud.kubernetes.discovery.namespaces=a,b");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 2);
});
}
@@ -192,11 +399,34 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
"spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.client.health-indicator.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("kubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).hasSingleBean(SharedInformerFactory.class);
assertThat(context).getBeans(SharedIndexInformer.class).hasSize(2);
assertThat(context).getBeans(Lister.class).hasSize(2);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertNonSelectiveNamespacesBeansPresent(context);
assertSelectiveNamespacesBeansMissing(context);
});
}
@Test
void healthEnabledClassNotPresentWithSelectiveNamespaces() {
setupWithFilteredClassLoader("org.springframework.boot.actuate.health.ReactiveHealthIndicator",
"spring.main.cloud-platform=KUBERNETES", "spring.cloud.config.enabled=false",
"spring.cloud.discovery.client.health-indicator.enabled=false",
"spring.cloud.kubernetes.discovery.namespaces=a,b");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(KubernetesInformerDiscoveryClient.class);
assertThat(context).hasBean("selectiveNamespacesKubernetesClientInformerDiscoveryClient");
assertThat(context).hasSingleBean(KubernetesInformerReactiveDiscoveryClient.class);
assertThat(context).doesNotHaveBean(ReactiveDiscoveryClientHealthIndicator.class);
assertThat(context).doesNotHaveBean(KubernetesDiscoveryClientHealthIndicatorInitializer.class);
assertNonSelectiveNamespacesBeansMissing(context);
assertSelectiveNamespacesBeansPresent(context, 2);
});
}
@@ -206,6 +436,7 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
KubernetesInformerReactiveDiscoveryClientAutoConfiguration.class,
KubernetesClientAutoConfiguration.class, SimpleReactiveDiscoveryClientAutoConfiguration.class,
UtilAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class,
KubernetesClientInformerSelectiveNamespacesAutoConfiguration.class,
KubernetesCommonsAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class))
.withUserConfiguration(ApiClientConfig.class).withPropertyValues(properties);
}
@@ -216,6 +447,7 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
KubernetesInformerReactiveDiscoveryClientAutoConfiguration.class,
KubernetesClientAutoConfiguration.class, SimpleReactiveDiscoveryClientAutoConfiguration.class,
UtilAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class,
KubernetesClientInformerSelectiveNamespacesAutoConfiguration.class,
KubernetesCommonsAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class))
.withUserConfiguration(ApiClientConfig.class).withClassLoader(new FilteredClassLoader(name))
.withPropertyValues(properties);

View File

@@ -37,14 +37,11 @@ import org.mockito.Mockito;
import reactor.test.StepVerifier;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
import org.springframework.cloud.kubernetes.client.discovery.KubernetesInformerDiscoveryClient;
import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
import org.springframework.mock.env.MockEnvironment;
import static io.kubernetes.client.util.Namespaces.NAMESPACE_ALL;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Ryan Baxter
@@ -88,13 +85,14 @@ class KubernetesInformerReactiveDiscoveryClientTests {
void testDiscoveryGetServicesAllNamespaceShouldWork() {
Lister<V1Service> serviceLister = setupServiceLister(NAMESPACE_ALL, TEST_SERVICE_1, TEST_SERVICE_2,
TEST_SERVICE_3);
Lister<V1Endpoints> endpointsLister = setupEndpointsLister("");
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, false);
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
new KubernetesNamespaceProvider(new MockEnvironment()), sharedInformerFactory, serviceLister, null,
null, null, kubernetesDiscoveryProperties);
new KubernetesInformerDiscoveryClient(sharedInformerFactory, serviceLister, endpointsLister, null, null,
kubernetesDiscoveryProperties));
StepVerifier.create(discoveryClient.getServices())
.expectNext(TEST_SERVICE_1.getMetadata().getName(), TEST_SERVICE_2.getMetadata().getName())
@@ -105,12 +103,11 @@ class KubernetesInformerReactiveDiscoveryClientTests {
@Test
void testDiscoveryGetServicesOneNamespaceShouldWork() {
Lister<V1Service> serviceLister = setupServiceLister(NAMESPACE_1, TEST_SERVICE_1, TEST_SERVICE_2);
Lister<V1Endpoints> endpointsLister = setupEndpointsLister("");
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
when(kubernetesNamespaceProvider.getNamespace()).thenReturn(NAMESPACE_1);
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
kubernetesNamespaceProvider, sharedInformerFactory, serviceLister, null, null, null,
KubernetesDiscoveryProperties.DEFAULT);
new KubernetesInformerDiscoveryClient(sharedInformerFactory, serviceLister, endpointsLister, null, null,
KubernetesDiscoveryProperties.DEFAULT));
StepVerifier.create(discoveryClient.getServices()).expectNext(TEST_SERVICE_1.getMetadata().getName())
.expectComplete().verify();
@@ -127,8 +124,8 @@ class KubernetesInformerReactiveDiscoveryClientTests {
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false);
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
new KubernetesNamespaceProvider(new MockEnvironment()), sharedInformerFactory, serviceLister,
endpointsLister, null, null, kubernetesDiscoveryProperties);
new KubernetesInformerDiscoveryClient(sharedInformerFactory, serviceLister, endpointsLister, null, null,
kubernetesDiscoveryProperties));
StepVerifier.create(discoveryClient.getInstances("test-svc-1"))
.expectNext(new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080,
@@ -147,11 +144,9 @@ class KubernetesInformerReactiveDiscoveryClientTests {
Set.of(), true, 60, false, null, Set.of(), Map.of(), null,
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false);
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
when(kubernetesNamespaceProvider.getNamespace()).thenReturn(NAMESPACE_1);
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
kubernetesNamespaceProvider, sharedInformerFactory, serviceLister, endpointsLister, null, null,
kubernetesDiscoveryProperties);
new KubernetesInformerDiscoveryClient(sharedInformerFactory, serviceLister, endpointsLister, null, null,
kubernetesDiscoveryProperties));
StepVerifier.create(discoveryClient.getInstances("test-svc-1"))
.expectNext(new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080,
@@ -172,6 +167,8 @@ class KubernetesInformerReactiveDiscoveryClientTests {
*/
@Test
void testAllNamespacesTwoServicesPresent() {
Lister<V1Endpoints> endpointsLister = setupEndpointsLister("");
boolean allNamespaces = true;
V1Service serviceA = new V1Service().metadata(new V1ObjectMeta().name("service-a").namespace("namespace-a"));
V1Service serviceB = new V1Service().metadata(new V1ObjectMeta().name("service-b").namespace("namespace-b"));
@@ -182,12 +179,9 @@ class KubernetesInformerReactiveDiscoveryClientTests {
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true,
allNamespaces, Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, false);
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
when(kubernetesNamespaceProvider.getNamespace()).thenReturn("irrelevant");
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
kubernetesNamespaceProvider, sharedInformerFactory, serviceLister, null, null, null,
kubernetesDiscoveryProperties);
new KubernetesInformerDiscoveryClient(sharedInformerFactory, serviceLister, endpointsLister, null, null,
kubernetesDiscoveryProperties));
List<String> result = discoveryClient.getServices().collectList().block();
Assertions.assertEquals(result.size(), 2);
@@ -207,6 +201,8 @@ class KubernetesInformerReactiveDiscoveryClientTests {
*/
@Test
void testSingleNamespaceTwoServicesPresent() {
Lister<V1Endpoints> endpointsLister = setupEndpointsLister("");
boolean allNamespaces = false;
V1Service serviceA = new V1Service().metadata(new V1ObjectMeta().name("service-a").namespace("namespace-a"));
V1Service serviceB = new V1Service().metadata(new V1ObjectMeta().name("service-b").namespace("namespace-b"));
@@ -217,12 +213,9 @@ class KubernetesInformerReactiveDiscoveryClientTests {
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true,
allNamespaces, Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, false);
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
when(kubernetesNamespaceProvider.getNamespace()).thenReturn("irrelevant");
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
kubernetesNamespaceProvider, sharedInformerFactory, serviceLister, null, null, null,
kubernetesDiscoveryProperties);
new KubernetesInformerDiscoveryClient(sharedInformerFactory, serviceLister, endpointsLister, null, null,
kubernetesDiscoveryProperties));
List<String> result = discoveryClient.getServices().collectList().block();
Assertions.assertEquals(result.size(), 1);
@@ -261,19 +254,16 @@ class KubernetesInformerReactiveDiscoveryClientTests {
endpointsCache.add(endpointsXNamespaceA);
endpointsCache.add(endpointsXNamespaceB);
Lister<V1Endpoints> endpointsLister = new Lister<>(endpointsCache).namespace(NAMESPACE_ALL);
Lister<V1Service> serviceLister = new Lister<>(serviceCache).namespace(NAMESPACE_ALL);
Lister<V1Endpoints> endpointsLister = new Lister<>(endpointsCache, NAMESPACE_ALL);
Lister<V1Service> serviceLister = new Lister<>(serviceCache, NAMESPACE_ALL);
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true,
allNamespaces, Set.of(), true, 60, false, null, Set.of(), Map.of(), null,
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false);
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
when(kubernetesNamespaceProvider.getNamespace()).thenReturn("irrelevant");
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
kubernetesNamespaceProvider, sharedInformerFactory, serviceLister, endpointsLister, null, null,
kubernetesDiscoveryProperties);
new KubernetesInformerDiscoveryClient(sharedInformerFactory, serviceLister, endpointsLister, null, null,
kubernetesDiscoveryProperties));
List<ServiceInstance> result = discoveryClient.getInstances("endpoints-x").collectList().block();
Assertions.assertEquals(result.size(), 2);
@@ -320,12 +310,9 @@ class KubernetesInformerReactiveDiscoveryClientTests {
allNamespaces, Set.of(), true, 60, false, null, Set.of(), Map.of(), null,
KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false);
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
when(kubernetesNamespaceProvider.getNamespace()).thenReturn("irrelevant");
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
kubernetesNamespaceProvider, sharedInformerFactory, serviceLister, endpointsLister, null, null,
kubernetesDiscoveryProperties);
new KubernetesInformerDiscoveryClient(sharedInformerFactory, serviceLister, endpointsLister, null, null,
kubernetesDiscoveryProperties));
List<ServiceInstance> result = discoveryClient.getInstances("endpoints-x").collectList().block();
Assertions.assertEquals(result.size(), 1);

View File

@@ -21,8 +21,10 @@ import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
@@ -45,4 +47,9 @@ public class ReactiveDiscoveryController {
return reactiveDiscoveryClient.getServices().collectList();
}
@GetMapping("reactive/service-instances/{serviceId}")
public Mono<List<ServiceInstance>> serviceInstances(@PathVariable("serviceId") String serviceId) {
return reactiveDiscoveryClient.getInstances(serviceId).collectList();
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.kubernetes.client.discovery;
package org.springframework.cloud.kubernetes.client.discovery.it;
import java.time.Duration;
import java.util.ArrayList;
@@ -31,7 +31,6 @@ import io.kubernetes.client.openapi.models.V1Service;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Container;
import org.testcontainers.k3s.K3sContainer;
@@ -92,7 +91,7 @@ class KubernetesClientDiscoveryClientIT {
manifests(false, null, Phase.CREATE);
util.busybox(NAMESPACE, Phase.CREATE);
assertLogStatement("serviceSharedInformer will use namespace : default");
Assertions.assertTrue(logs().contains("serviceSharedInformer will use namespace : default"));
WebClient servicesClient = builder().baseUrl("http://localhost/services").build();
@@ -107,7 +106,7 @@ class KubernetesClientDiscoveryClientIT {
Assertions.assertTrue(servicesResult.contains("busybox-service"));
WebClient ourServiceClient = builder()
.baseUrl("http://localhost//service-instances/spring-cloud-kubernetes-client-discovery-it").build();
.baseUrl("http://localhost/service-instances/spring-cloud-kubernetes-client-discovery-it").build();
List<DefaultKubernetesServiceInstance> ourServiceInstances = ourServiceClient.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
@@ -126,7 +125,7 @@ class KubernetesClientDiscoveryClientIT {
Assertions.assertEquals(serviceInstance.getPort(), 8080);
Assertions.assertEquals(serviceInstance.getNamespace(), "default");
WebClient busyBoxServiceClient = builder().baseUrl("http://localhost//service-instances/busybox-service")
WebClient busyBoxServiceClient = builder().baseUrl("http://localhost/service-instances/busybox-service")
.build();
List<DefaultKubernetesServiceInstance> busyBoxServiceInstances = busyBoxServiceClient.method(HttpMethod.GET)
.retrieve().bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
@@ -169,7 +168,7 @@ class KubernetesClientDiscoveryClientIT {
util.busybox(NAMESPACE_B, Phase.CREATE);
manifests(true, null, Phase.CREATE);
assertLogStatement("serviceSharedInformer will use all-namespaces");
Assertions.assertTrue(logs().contains("serviceSharedInformer will use all-namespaces"));
WebClient servicesClient = builder().baseUrl("http://localhost/services").build();
List<String> servicesResult = servicesClient.method(HttpMethod.GET).retrieve()
@@ -211,20 +210,21 @@ class KubernetesClientDiscoveryClientIT {
* </pre>
*/
@Test
@Disabled
// TODO will be fixed by this issue :
// https://github.com/spring-cloud/spring-cloud-kubernetes/issues/1289
void testSpecificNamespace() {
util.createNamespace(NAMESPACE_A);
util.createNamespace(NAMESPACE_B);
util.setUpClusterWide(NAMESPACE, Set.of(NAMESPACE_A));
util.setUpClusterWide(NAMESPACE, Set.of(NAMESPACE, NAMESPACE_A));
util.wiremock(NAMESPACE_A, "/wiremock", Phase.CREATE);
util.wiremock(NAMESPACE_B, "/wiremock", Phase.CREATE);
manifests(false, NAMESPACE_A, Phase.CREATE);
// first check that wiremock service is present in both namespaces a and b
assertServicePresentInNamespaces(List.of("a", "b"), "service-wiremock", "service-wiremock");
assertLogStatement("serviceSharedInformer will use namespace : a");
String logs = logs();
Assertions.assertTrue(logs.contains("using selective namespaces : [a]"));
Assertions.assertTrue(logs.contains("reading pod in namespace : default"));
Assertions.assertTrue(logs.contains("registering lister (for services) in namespace : a"));
Assertions.assertTrue(logs.contains("registering lister (for endpoints) in namespace : a"));
WebClient servicesClient = builder().baseUrl("http://localhost/services").build();
List<String> servicesResult = servicesClient.method(HttpMethod.GET).retrieve()
@@ -234,7 +234,7 @@ class KubernetesClientDiscoveryClientIT {
Assertions.assertEquals(servicesResult.size(), 1);
Assertions.assertTrue(servicesResult.contains("service-wiremock"));
WebClient wiremockInNamespaceAClient = builder().baseUrl("http://localhost//service-instances/service-wiremock")
WebClient wiremockInNamespaceAClient = builder().baseUrl("http://localhost/service-instances/service-wiremock")
.build();
List<DefaultKubernetesServiceInstance> wiremockInNamespaceA = wiremockInNamespaceAClient.method(HttpMethod.GET)
@@ -261,7 +261,7 @@ class KubernetesClientDiscoveryClientIT {
manifests(false, NAMESPACE_A, Phase.DELETE);
util.wiremock(NAMESPACE_A, "/wiremock", Phase.DELETE);
util.wiremock(NAMESPACE_B, "/wiremock", Phase.DELETE);
util.deleteClusterWide(NAMESPACE, Set.of(NAMESPACE_A));
util.deleteClusterWide(NAMESPACE, Set.of(NAMESPACE, NAMESPACE_A));
util.deleteNamespace(NAMESPACE_A);
util.deleteNamespace(NAMESPACE_B);
}
@@ -276,6 +276,10 @@ class KubernetesClientDiscoveryClientIT {
.orElse(List.of()));
V1EnvVar debugLevel = new V1EnvVar().name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_DISCOVERY")
.value("DEBUG");
V1EnvVar debugLevelForClient = new V1EnvVar().name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT")
.value("DEBUG");
if (allNamespaces) {
V1EnvVar allNamespacesVar = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_DISCOVERY_ALL_NAMESPACES")
.value("TRUE");
@@ -283,11 +287,12 @@ class KubernetesClientDiscoveryClientIT {
}
if (clientSpecificNamespace != null) {
V1EnvVar clientNamespace = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_CLIENT_NAMESPACE")
V1EnvVar clientNamespace = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0")
.value(NAMESPACE_A);
envVars.add(clientNamespace);
}
envVars.add(debugLevel);
envVars.add(debugLevelForClient);
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
if (phase.equals(Phase.CREATE)) {
@@ -306,14 +311,13 @@ class KubernetesClientDiscoveryClientIT {
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
}
private void assertLogStatement(String message) {
private String logs() {
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.assertTrue(ok.contains(message));
return execResult.getStdout();
}
catch (Exception e) {
e.printStackTrace();

View File

@@ -0,0 +1,326 @@
/*
* 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.client.discovery.it;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import io.kubernetes.client.openapi.models.V1Deployment;
import io.kubernetes.client.openapi.models.V1EnvVar;
import io.kubernetes.client.openapi.models.V1Ingress;
import io.kubernetes.client.openapi.models.V1Service;
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.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.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.native_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 KubernetesClientDiscoveryMultipleSelectiveNamespacesIT {
private static final String BLOCKING_PUBLISH = "Will publish InstanceRegisteredEvent from blocking implementation";
private static final String REACTIVE_PUBLISH = "Will publish InstanceRegisteredEvent from reactive implementation";
private static final String NAMESPACE = "default";
private static final String NAMESPACE_A = "a";
private static final String NAMESPACE_B = "b";
private static final String IMAGE_NAME = "spring-cloud-kubernetes-client-discovery-it";
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);
util.createNamespace(NAMESPACE_A);
util.createNamespace(NAMESPACE_B);
util.setUpClusterWide(NAMESPACE, Set.of(NAMESPACE, NAMESPACE_A, NAMESPACE_B));
util.wiremock(NAMESPACE, "/wiremock", Phase.CREATE);
util.wiremock(NAMESPACE_A, "/wiremock", Phase.CREATE);
util.wiremock(NAMESPACE_B, "/wiremock", Phase.CREATE);
}
@AfterAll
static void afterAll() throws Exception {
Commons.cleanUp(IMAGE_NAME, K3S);
util.wiremock(NAMESPACE, "/wiremock", Phase.DELETE);
util.wiremock(NAMESPACE_A, "/wiremock", Phase.DELETE);
util.wiremock(NAMESPACE_B, "/wiremock", Phase.DELETE);
util.deleteClusterWide(NAMESPACE, Set.of(NAMESPACE, NAMESPACE_A, NAMESPACE_B));
util.deleteNamespace(NAMESPACE_A);
util.deleteNamespace(NAMESPACE_B);
}
/**
* Deploy wiremock in 3 namespaces: default, a, b. Search in selective namespaces 'a'
* and 'b' with blocking enabled and reactive disabled, as such find services and it's
* instances.
*/
@Test
void testTwoNamespacesBlockingOnly() {
manifests(Phase.CREATE, false, true);
String logs = logs();
Assertions.assertTrue(logs.contains("using selective namespaces : [a, b]"));
Assertions.assertTrue(
logs.contains("ConditionalOnSelectiveNamespacesMissing : found selective namespaces : [a, b]"));
Assertions.assertTrue(
logs.contains("ConditionalOnSelectiveNamespacesPresent : found selective namespaces : [a, b]"));
Assertions.assertTrue(logs.contains("registering lister (for services) in namespace : a"));
Assertions.assertTrue(logs.contains("registering lister (for services) in namespace : b"));
Assertions.assertTrue(logs.contains("registering lister (for endpoints) in namespace : a"));
Assertions.assertTrue(logs.contains("registering lister (for endpoints) in namespace : b"));
// this tiny checks makes sure that blocking is enabled and reactive is disabled.
Assertions.assertTrue(logs.contains(BLOCKING_PUBLISH));
Assertions.assertFalse(logs.contains(REACTIVE_PUBLISH));
blockingCheck();
manifests(Phase.DELETE, false, true);
}
/**
* Deploy wiremock in 3 namespaces: default, a, b. Search in selective namespaces 'a'
* and 'b' with blocking disabled and reactive enabled, as such find services and it's
* instances.
*/
@Test
void testTwoNamespaceReactiveOnly() {
manifests(Phase.CREATE, true, false);
String logs = logs();
Assertions.assertTrue(logs.contains("using selective namespaces : [a, b]"));
Assertions.assertTrue(
logs.contains("ConditionalOnSelectiveNamespacesMissing : found selective namespaces : [a, b]"));
Assertions.assertTrue(
logs.contains("ConditionalOnSelectiveNamespacesPresent : found selective namespaces : [a, b]"));
Assertions.assertTrue(logs.contains("registering lister (for services) in namespace : a"));
Assertions.assertTrue(logs.contains("registering lister (for services) in namespace : b"));
Assertions.assertTrue(logs.contains("registering lister (for endpoints) in namespace : a"));
Assertions.assertTrue(logs.contains("registering lister (for endpoints) in namespace : b"));
// this tiny checks makes sure that blocking is disabled and reactive is enabled.
Assertions.assertFalse(logs.contains(BLOCKING_PUBLISH));
Assertions.assertTrue(logs.contains(REACTIVE_PUBLISH));
reactiveCheck();
manifests(Phase.DELETE, true, false);
}
/**
* Deploy wiremock in 3 namespaces: default, a, b. Search in selective namespaces 'a'
* and 'b' with blocking enabled and reactive enabled, as such find services and its
* service instances.
*/
@Test
void testTwoNamespacesBothBlockingAndReactive() {
manifests(Phase.CREATE, false, false);
String logs = logs();
Assertions.assertTrue(logs.contains("using selective namespaces : [a, b]"));
Assertions.assertTrue(
logs.contains("ConditionalOnSelectiveNamespacesMissing : found selective namespaces : [a, b]"));
Assertions.assertTrue(
logs.contains("ConditionalOnSelectiveNamespacesPresent : found selective namespaces : [a, b]"));
Assertions.assertTrue(logs.contains("registering lister (for services) in namespace : a"));
Assertions.assertTrue(logs.contains("registering lister (for services) in namespace : b"));
Assertions.assertTrue(logs.contains("registering lister (for endpoints) in namespace : a"));
Assertions.assertTrue(logs.contains("registering lister (for endpoints) in namespace : b"));
// this tiny checks makes sure that blocking is enabled and reactive is enabled.
Assertions.assertTrue(logs.contains(BLOCKING_PUBLISH));
Assertions.assertTrue(logs.contains(REACTIVE_PUBLISH));
blockingCheck();
reactiveCheck();
manifests(Phase.DELETE, false, false);
}
private static void manifests(Phase phase, boolean disableBlocking, boolean disableReactive) {
V1Deployment deployment = (V1Deployment) util.yaml("kubernetes-discovery-deployment.yaml");
V1Service service = (V1Service) util.yaml("kubernetes-discovery-service.yaml");
V1Ingress ingress = (V1Ingress) util.yaml("kubernetes-discovery-ingress.yaml");
List<V1EnvVar> envVars = new ArrayList<>(
Optional.ofNullable(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv())
.orElse(List.of()));
V1EnvVar debugLevel = new V1EnvVar().name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_DISCOVERY")
.value("DEBUG");
V1EnvVar selectiveNamespaceA = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0")
.value(NAMESPACE_A);
V1EnvVar selectiveNamespaceB = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_1")
.value(NAMESPACE_B);
if (disableReactive) {
V1EnvVar disableReactiveEnvVar = new V1EnvVar().name("SPRING_CLOUD_DISCOVERY_REACTIVE_ENABLED")
.value("FALSE");
envVars.add(disableReactiveEnvVar);
}
if (disableBlocking) {
V1EnvVar disableBlockingEnvVar = new V1EnvVar().name("SPRING_CLOUD_DISCOVERY_BLOCKING_ENABLED")
.value("FALSE");
envVars.add(disableBlockingEnvVar);
}
envVars.add(debugLevel);
envVars.add(selectiveNamespaceA);
envVars.add(selectiveNamespaceB);
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
if (phase.equals(Phase.CREATE)) {
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
}
else if (phase.equals(Phase.DELETE)) {
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
}
}
private String logs() {
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());
return execResult.getStdout();
}
catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private void reactiveCheck() {
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();
// we get two here, but since there is 'distinct' call, only 1 will be reported
// but service instances will report 2 nevertheless
Assertions.assertEquals(servicesResult.size(), 1);
Assertions.assertTrue(servicesResult.contains("service-wiremock"));
WebClient ourServiceClient = builder().baseUrl("http://localhost/reactive/service-instances/service-wiremock")
.build();
List<DefaultKubernetesServiceInstance> ourServiceInstances = ourServiceClient.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(ourServiceInstances.size(), 2);
ourServiceInstances = ourServiceInstances.stream()
.sorted(Comparator.comparing(DefaultKubernetesServiceInstance::namespace)).toList();
DefaultKubernetesServiceInstance serviceInstanceA = ourServiceInstances.get(0);
// we only care about namespace here, as all other fields are tested in various
// other tests.
Assertions.assertEquals(serviceInstanceA.getNamespace(), "a");
DefaultKubernetesServiceInstance serviceInstanceB = ourServiceInstances.get(1);
// we only care about namespace here, as all other fields are tested in various
// other tests.
Assertions.assertEquals(serviceInstanceB.getNamespace(), "b");
}
private void blockingCheck() {
WebClient servicesClient = builder().baseUrl("http://localhost/services").build();
List<String> servicesResult = servicesClient.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<String>>() {
}).retryWhen(retrySpec()).block();
// we get two here, but since there is 'distinct' call, only 1 will be reported
// but service instances will report 2 nevertheless
Assertions.assertEquals(servicesResult.size(), 1);
Assertions.assertTrue(servicesResult.contains("service-wiremock"));
WebClient ourServiceClient = builder().baseUrl("http://localhost/service-instances/service-wiremock").build();
List<DefaultKubernetesServiceInstance> ourServiceInstances = ourServiceClient.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(ourServiceInstances.size(), 2);
ourServiceInstances = ourServiceInstances.stream()
.sorted(Comparator.comparing(DefaultKubernetesServiceInstance::namespace)).toList();
DefaultKubernetesServiceInstance serviceInstanceA = ourServiceInstances.get(0);
// we only care about namespace here, as all other fields are tested in various
// other tests.
Assertions.assertEquals(serviceInstanceA.getNamespace(), "a");
DefaultKubernetesServiceInstance serviceInstanceB = ourServiceInstances.get(1);
// we only care about namespace here, as all other fields are tested in various
// other tests.
Assertions.assertEquals(serviceInstanceB.getNamespace(), "b");
}
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,297 @@
/*
* 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.client.discovery.it;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import io.kubernetes.client.openapi.models.V1Deployment;
import io.kubernetes.client.openapi.models.V1EnvVar;
import io.kubernetes.client.openapi.models.V1Ingress;
import io.kubernetes.client.openapi.models.V1Service;
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.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.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.native_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 KubernetesClientDiscoverySingleSelectiveNamespaceIT {
private static final String BLOCKING_PUBLISH = "Will publish InstanceRegisteredEvent from blocking implementation";
private static final String REACTIVE_PUBLISH = "Will publish InstanceRegisteredEvent from reactive implementation";
private static final String NAMESPACE = "default";
private static final String NAMESPACE_A = "a";
private static final String NAMESPACE_B = "b";
private static final String IMAGE_NAME = "spring-cloud-kubernetes-client-discovery-it";
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);
util.createNamespace(NAMESPACE_A);
util.createNamespace(NAMESPACE_B);
util.setUpClusterWide(NAMESPACE, Set.of(NAMESPACE, NAMESPACE_A, NAMESPACE_B));
util.wiremock(NAMESPACE, "/wiremock", Phase.CREATE);
util.wiremock(NAMESPACE_A, "/wiremock", Phase.CREATE);
util.wiremock(NAMESPACE_B, "/wiremock", Phase.CREATE);
}
@AfterAll
static void after() throws Exception {
Commons.cleanUp(IMAGE_NAME, K3S);
util.wiremock(NAMESPACE, "/wiremock", Phase.DELETE);
util.wiremock(NAMESPACE_A, "/wiremock", Phase.DELETE);
util.wiremock(NAMESPACE_B, "/wiremock", Phase.DELETE);
util.deleteClusterWide(NAMESPACE, Set.of(NAMESPACE, NAMESPACE_A, NAMESPACE_B));
util.deleteNamespace(NAMESPACE_A);
util.deleteNamespace(NAMESPACE_B);
}
/**
* Deploy wiremock in 3 namespaces: default, a, b. Search only in selective namespace
* 'a' with blocking enabled and reactive disabled, as such find a single service and
* its service instance.
*/
@Test
void testOneNamespaceBlockingOnly() {
manifests(Phase.CREATE, false, true);
String logs = logs();
Assertions.assertTrue(logs.contains("using selective namespaces : [a]"));
Assertions.assertTrue(
logs.contains("ConditionalOnSelectiveNamespacesMissing : found selective namespaces : [a]"));
Assertions.assertTrue(
logs.contains("ConditionalOnSelectiveNamespacesPresent : found selective namespaces : [a]"));
Assertions.assertTrue(logs.contains("registering lister (for services) in namespace : a"));
Assertions.assertTrue(logs.contains("registering lister (for endpoints) in namespace : a"));
// this tiny checks makes sure that blocking is enabled and reactive is disabled.
Assertions.assertTrue(logs.contains(BLOCKING_PUBLISH));
Assertions.assertFalse(logs.contains(REACTIVE_PUBLISH));
blockingCheck();
manifests(Phase.DELETE, false, true);
}
/**
* Deploy wiremock in 3 namespaces: default, a, b. Search only in selective namespace
* 'a' with blocking disabled and reactive enabled, as such find a single service and
* its service instance.
*/
@Test
void testOneNamespaceReactiveOnly() {
manifests(Phase.CREATE, true, false);
String logs = logs();
Assertions.assertTrue(logs.contains("using selective namespaces : [a]"));
Assertions.assertTrue(
logs.contains("ConditionalOnSelectiveNamespacesMissing : found selective namespaces : [a]"));
Assertions.assertTrue(
logs.contains("ConditionalOnSelectiveNamespacesPresent : found selective namespaces : [a]"));
Assertions.assertTrue(logs.contains("registering lister (for services) in namespace : a"));
Assertions.assertTrue(logs.contains("registering lister (for endpoints) in namespace : a"));
// this tiny checks makes sure that reactive is enabled and blocking is disabled.
Assertions.assertFalse(logs.contains(BLOCKING_PUBLISH));
Assertions.assertTrue(logs.contains(REACTIVE_PUBLISH));
reactiveCheck();
manifests(Phase.DELETE, true, false);
}
/**
* Deploy wiremock in 3 namespaces: default, a, b. Search only in selective namespace
* 'a' with blocking enabled and reactive enabled, as such find a single service and
* its service instance.
*/
@Test
void testOneNamespaceBothBlockingAndReactive() {
manifests(Phase.CREATE, false, false);
String logs = logs();
Assertions.assertTrue(logs.contains("using selective namespaces : [a]"));
Assertions.assertTrue(
logs.contains("ConditionalOnSelectiveNamespacesMissing : found selective namespaces : [a]"));
Assertions.assertTrue(
logs.contains("ConditionalOnSelectiveNamespacesPresent : found selective namespaces : [a]"));
Assertions.assertTrue(logs.contains("registering lister (for services) in namespace : a"));
Assertions.assertTrue(logs.contains("registering lister (for endpoints) in namespace : a"));
// this tiny checks makes sure that blocking and reactive is enabled.
Assertions.assertTrue(logs.contains(BLOCKING_PUBLISH));
Assertions.assertTrue(logs.contains(REACTIVE_PUBLISH));
blockingCheck();
reactiveCheck();
manifests(Phase.DELETE, false, false);
}
private static void manifests(Phase phase, boolean disableBlocking, boolean disableReactive) {
V1Deployment deployment = (V1Deployment) util.yaml("kubernetes-discovery-deployment.yaml");
V1Service service = (V1Service) util.yaml("kubernetes-discovery-service.yaml");
V1Ingress ingress = (V1Ingress) util.yaml("kubernetes-discovery-ingress.yaml");
List<V1EnvVar> envVars = new ArrayList<>(
Optional.ofNullable(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv())
.orElse(List.of()));
V1EnvVar debugLevel = new V1EnvVar().name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_DISCOVERY")
.value("DEBUG");
V1EnvVar selectiveNamespaceA = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0")
.value(NAMESPACE_A);
if (disableReactive) {
V1EnvVar disableReactiveEnvVar = new V1EnvVar().name("SPRING_CLOUD_DISCOVERY_REACTIVE_ENABLED")
.value("FALSE");
envVars.add(disableReactiveEnvVar);
}
if (disableBlocking) {
V1EnvVar disableBlockingEnvVar = new V1EnvVar().name("SPRING_CLOUD_DISCOVERY_BLOCKING_ENABLED")
.value("FALSE");
envVars.add(disableBlockingEnvVar);
}
envVars.add(debugLevel);
envVars.add(selectiveNamespaceA);
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
if (phase.equals(Phase.CREATE)) {
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
}
else if (phase.equals(Phase.DELETE)) {
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
}
}
private void reactiveCheck() {
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.assertEquals(servicesResult.size(), 1);
Assertions.assertTrue(servicesResult.contains("service-wiremock"));
WebClient ourServiceClient = builder().baseUrl("http://localhost/reactive/service-instances/service-wiremock")
.build();
List<DefaultKubernetesServiceInstance> ourServiceInstances = ourServiceClient.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(ourServiceInstances.size(), 1);
DefaultKubernetesServiceInstance serviceInstance = ourServiceInstances.get(0);
// we only care about namespace here, as all other fields are tested in various
// other tests.
Assertions.assertEquals(serviceInstance.getNamespace(), "a");
}
private void blockingCheck() {
WebClient servicesClient = builder().baseUrl("http://localhost/services").build();
List<String> servicesResult = servicesClient.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<String>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(servicesResult.size(), 1);
Assertions.assertTrue(servicesResult.contains("service-wiremock"));
WebClient ourServiceClient = builder().baseUrl("http://localhost/service-instances/service-wiremock").build();
List<DefaultKubernetesServiceInstance> ourServiceInstances = ourServiceClient.method(HttpMethod.GET).retrieve()
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
}).retryWhen(retrySpec()).block();
Assertions.assertEquals(ourServiceInstances.size(), 1);
DefaultKubernetesServiceInstance serviceInstance = ourServiceInstances.get(0);
// we only care about namespace here, as all other fields are tested in various
// other tests.
Assertions.assertEquals(serviceInstance.getNamespace(), "a");
}
private String logs() {
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());
return execResult.getStdout();
}
catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
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);
}
}