New Kubernetes Informer Auto Config (#1276)
This commit is contained in:
@@ -36,8 +36,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* itself as the default apiClient. This is to avoid overwriting the user's
|
||||
* defaultApiClient if they include this project.
|
||||
*
|
||||
* kubernetes informer is disabled because KubernetesInformerAutoConfiguration creates a
|
||||
* defaultApiClient that will be autowired instead of the ApiClient created in
|
||||
* kubernetes informer is disabled because KubernetesClientInformerAutoConfiguration
|
||||
* creates a defaultApiClient that will be autowired instead of the ApiClient created in
|
||||
* KubernetesClientAutoConfiguration
|
||||
*/
|
||||
@SpringBootTest(classes = App.class,
|
||||
|
||||
@@ -37,8 +37,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* itself as the default apiClient. This is to avoid overwriting the user's
|
||||
* defaultApiClient if they include this project.
|
||||
*
|
||||
* kubernetes informer is disabled because KubernetesInformerAutoConfiguration creates a
|
||||
* defaultApiClient that will be autowired instead of the ApiClient created in
|
||||
* kubernetes informer is disabled because KubernetesClientInformerAutoConfiguration
|
||||
* creates a defaultApiClient that will be autowired instead of the ApiClient created in
|
||||
* KubernetesClientAutoConfiguration
|
||||
*/
|
||||
@SpringBootTest(classes = App.class, properties = { "kubernetes.informer.enabled=false",
|
||||
|
||||
@@ -31,8 +31,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* itself as the default apiClient. This is to avoid overwriting the user's
|
||||
* defaultApiClient if they include this project.
|
||||
*
|
||||
* kubernetes informer is disabled because KubernetesInformerAutoConfiguration creates a
|
||||
* defaultApiClient that will be autowired instead of the ApiClient created in
|
||||
* kubernetes informer is disabled because KubernetesClientInformerAutoConfiguration
|
||||
* creates a defaultApiClient that will be autowired instead of the ApiClient created in
|
||||
* KubernetesClientAutoConfiguration
|
||||
*/
|
||||
@SpringBootTest(classes = App.class,
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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 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.KubernetesNamespaceProvider;
|
||||
import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException;
|
||||
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.Configuration;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
|
||||
import static io.kubernetes.client.util.Namespaces.NAMESPACE_ALL;
|
||||
import static io.kubernetes.client.util.Namespaces.NAMESPACE_DEFAULT;
|
||||
import static org.springframework.cloud.kubernetes.client.KubernetesClientUtils.getApplicationNamespace;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnDiscoveryEnabled
|
||||
@ConditionalOnKubernetesDiscoveryEnabled
|
||||
@ConditionalOnBlockingOrReactiveEnabled
|
||||
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
|
||||
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class })
|
||||
@AutoConfigureAfter({ KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class })
|
||||
public class KubernetesClientInformerAutoConfiguration {
|
||||
|
||||
private static final LogAccessor LOG = new LogAccessor(
|
||||
LogFactory.getLog(KubernetesClientInformerAutoConfiguration.class));
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public SharedInformerFactory sharedInformerFactory(ApiClient client) {
|
||||
return new SharedInformerFactory(client);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public String kubernetesClientNamespace(KubernetesDiscoveryProperties properties,
|
||||
KubernetesNamespaceProvider provider) {
|
||||
String namespace;
|
||||
if (properties.allNamespaces()) {
|
||||
namespace = NAMESPACE_ALL;
|
||||
LOG.debug(() -> "serviceSharedInformer will use all-namespaces");
|
||||
}
|
||||
else {
|
||||
try {
|
||||
namespace = getApplicationNamespace(null, "kubernetes client discovery", provider);
|
||||
}
|
||||
catch (NamespaceResolutionFailedException ex) {
|
||||
LOG.warn(() -> "failed to resolve namespace, defaulting to :" + NAMESPACE_DEFAULT
|
||||
+ ". This will fail in a future release.");
|
||||
namespace = NAMESPACE_DEFAULT;
|
||||
}
|
||||
LOG.debug("serviceSharedInformer will use namespace : " + namespace);
|
||||
}
|
||||
|
||||
return namespace;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(value = V1Service.class, parameterizedContainer = SharedIndexInformer.class)
|
||||
public SharedIndexInformer<V1Service> servicesSharedIndexInformer(SharedInformerFactory sharedInformerFactory,
|
||||
ApiClient apiClient, String kubernetesClientNamespace) {
|
||||
|
||||
GenericKubernetesApi<V1Service, V1ServiceList> servicesApi = new GenericKubernetesApi<>(V1Service.class,
|
||||
V1ServiceList.class, "", "v1", "services", apiClient);
|
||||
|
||||
return sharedInformerFactory.sharedIndexInformerFor(servicesApi, V1Service.class, 0L,
|
||||
kubernetesClientNamespace);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(value = V1Endpoints.class, parameterizedContainer = SharedIndexInformer.class)
|
||||
public SharedIndexInformer<V1Endpoints> endpointsSharedIndexInformer(SharedInformerFactory sharedInformerFactory,
|
||||
ApiClient apiClient, String kubernetesClientNamespace) {
|
||||
|
||||
GenericKubernetesApi<V1Endpoints, V1EndpointsList> servicesApi = new GenericKubernetesApi<>(V1Endpoints.class,
|
||||
V1EndpointsList.class, "", "v1", "endpoints", apiClient);
|
||||
|
||||
return sharedInformerFactory.sharedIndexInformerFor(servicesApi, V1Endpoints.class, 0L,
|
||||
kubernetesClientNamespace);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(value = V1Service.class, parameterizedContainer = Lister.class)
|
||||
public Lister<V1Service> servicesLister(SharedIndexInformer<V1Service> servicesSharedIndexInformer,
|
||||
String kubernetesClientNamespace) {
|
||||
return new Lister<>(servicesSharedIndexInformer.getIndexer(), kubernetesClientNamespace);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(value = V1Endpoints.class, parameterizedContainer = Lister.class)
|
||||
public Lister<V1Endpoints> endpointsLister(SharedIndexInformer<V1Endpoints> endpointsSharedIndexInformer,
|
||||
String kubernetesClientNamespace) {
|
||||
return new Lister<>(endpointsSharedIndexInformer.getIndexer(), kubernetesClientNamespace);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,8 +48,12 @@ import static io.kubernetes.client.util.Namespaces.NAMESPACE_ALL;
|
||||
import static io.kubernetes.client.util.Namespaces.NAMESPACE_DEFAULT;
|
||||
|
||||
/**
|
||||
* This configuration is not used by us internally and will be removed in a future
|
||||
* release. Use it at your own risk.
|
||||
*
|
||||
* @author wind57
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnDiscoveryEnabled
|
||||
@ConditionalOnKubernetesDiscoveryEnabled
|
||||
|
||||
@@ -72,38 +72,40 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
|
||||
|
||||
private final KubernetesDiscoveryProperties properties;
|
||||
|
||||
private final String namespace;
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public KubernetesInformerDiscoveryClient(String namespace, SharedInformerFactory sharedInformerFactory,
|
||||
Lister<V1Service> serviceLister, Lister<V1Endpoints> endpointsLister,
|
||||
SharedInformer<V1Service> serviceInformer, SharedInformer<V1Endpoints> endpointsInformer,
|
||||
KubernetesDiscoveryProperties properties) {
|
||||
this.namespace = namespace;
|
||||
this.sharedInformerFactory = sharedInformerFactory;
|
||||
|
||||
this.serviceLister = serviceLister;
|
||||
this.endpointsLister = endpointsLister;
|
||||
this.informersReadyFunc = () -> serviceInformer.hasSynced() && endpointsInformer.hasSynced();
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public KubernetesInformerDiscoveryClient(SharedInformerFactory sharedInformerFactory,
|
||||
Lister<V1Service> serviceLister, Lister<V1Endpoints> endpointsLister,
|
||||
SharedInformer<V1Service> serviceInformer, SharedInformer<V1Endpoints> endpointsInformer,
|
||||
KubernetesDiscoveryProperties properties) {
|
||||
this.sharedInformerFactory = sharedInformerFactory;
|
||||
this.serviceLister = serviceLister;
|
||||
this.endpointsLister = endpointsLister;
|
||||
this.informersReadyFunc = () -> serviceInformer.hasSynced() && endpointsInformer.hasSynced();
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Fabric8 Kubernetes Client Discovery";
|
||||
return "Kubernetes Client Discovery";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ServiceInstance> getInstances(String serviceId) {
|
||||
Objects.requireNonNull(serviceId, "serviceId must be provided");
|
||||
|
||||
if (!StringUtils.hasText(namespace) && !properties.allNamespaces()) {
|
||||
LOG.warn(() -> "Namespace is null or empty, this may cause issues looking up services");
|
||||
}
|
||||
|
||||
List<V1Service> services = properties.allNamespaces()
|
||||
? serviceLister.list().stream().filter(svc -> serviceId.equals(svc.getMetadata().getName())).toList()
|
||||
: List.of(serviceLister.namespace(namespace).get(serviceId));
|
||||
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<>();
|
||||
@@ -226,10 +228,8 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
|
||||
|
||||
@Override
|
||||
public List<String> getServices() {
|
||||
List<V1Service> services = properties.allNamespaces() ? serviceLister.list()
|
||||
: serviceLister.namespace(namespace).list();
|
||||
return services.stream().filter(service -> matchesServiceLabels(service, properties))
|
||||
.map(s -> s.getMetadata().getName()).collect(Collectors.toList());
|
||||
return serviceLister.list().stream().filter(service -> matchesServiceLabels(service, properties))
|
||||
.map(s -> s.getMetadata().getName()).distinct().toList();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
|
||||
@@ -55,7 +55,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
|
||||
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class })
|
||||
@AutoConfigureAfter({ KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class,
|
||||
KubernetesInformerAutoConfiguration.class })
|
||||
KubernetesClientInformerAutoConfiguration.class })
|
||||
public class KubernetesInformerDiscoveryClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -66,6 +66,7 @@ public class KubernetesInformerDiscoveryClientAutoConfiguration {
|
||||
return new KubernetesDiscoveryClientHealthIndicatorInitializer(podUtils, applicationEventPublisher);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public KubernetesInformerDiscoveryClient kubernetesInformerDiscoveryClient(
|
||||
@@ -77,4 +78,14 @@ public class KubernetesInformerDiscoveryClientAutoConfiguration {
|
||||
serviceLister, endpointsLister, serviceInformer, endpointsInformer, properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public KubernetesInformerDiscoveryClient kubernetesClientInformerDiscoveryClient(
|
||||
SharedInformerFactory sharedInformerFactory, Lister<V1Service> serviceLister,
|
||||
Lister<V1Endpoints> endpointsLister, SharedInformer<V1Service> serviceInformer,
|
||||
SharedInformer<V1Endpoints> endpointsInformer, KubernetesDiscoveryProperties properties) {
|
||||
return new KubernetesInformerDiscoveryClient(sharedInformerFactory, serviceLister, endpointsLister,
|
||||
serviceInformer, endpointsInformer, properties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ 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.KubernetesInformerAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.ConditionalOnKubernetesDiscoveryEnabled;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
@@ -58,7 +58,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
@AutoConfigureBefore({ SimpleReactiveDiscoveryClientAutoConfiguration.class,
|
||||
ReactiveCommonsClientAutoConfiguration.class })
|
||||
@AutoConfigureAfter({ ReactiveCompositeDiscoveryClientAutoConfiguration.class,
|
||||
KubernetesDiscoveryPropertiesAutoConfiguration.class, KubernetesInformerAutoConfiguration.class })
|
||||
KubernetesDiscoveryPropertiesAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class })
|
||||
public class KubernetesInformerReactiveDiscoveryClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
org.springframework.cloud.kubernetes.client.discovery.catalog.KubernetesCatalogWatchAutoConfiguration
|
||||
org.springframework.cloud.kubernetes.client.discovery.KubernetesInformerDiscoveryClientAutoConfiguration
|
||||
org.springframework.cloud.kubernetes.client.discovery.KubernetesInformerAutoConfiguration
|
||||
org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerAutoConfiguration
|
||||
org.springframework.cloud.kubernetes.client.discovery.reactive.KubernetesInformerReactiveDiscoveryClientAutoConfiguration
|
||||
|
||||
@@ -207,7 +207,7 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
|
||||
applicationContextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(KubernetesInformerDiscoveryClientAutoConfiguration.class,
|
||||
KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class,
|
||||
KubernetesInformerAutoConfiguration.class))
|
||||
KubernetesClientInformerAutoConfiguration.class))
|
||||
.withUserConfiguration(ApiClientConfig.class).withPropertyValues(properties);
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ class KubernetesInformerDiscoveryClientAutoConfigurationApplicationContextTests
|
||||
applicationContextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(KubernetesInformerDiscoveryClientAutoConfiguration.class,
|
||||
KubernetesClientAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class,
|
||||
KubernetesInformerAutoConfiguration.class))
|
||||
KubernetesClientInformerAutoConfiguration.class))
|
||||
.withClassLoader(new FilteredClassLoader(cls)).withUserConfiguration(ApiClientConfig.class)
|
||||
.withPropertyValues(properties);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscov
|
||||
import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration;
|
||||
import org.springframework.cloud.commons.util.UtilAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.client.discovery.KubernetesInformerAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.client.discovery.KubernetesClientInformerAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.commons.KubernetesCommonsAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryPropertiesAutoConfiguration;
|
||||
|
||||
@@ -189,7 +189,7 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
|
||||
KubernetesInformerReactiveDiscoveryClientAutoConfiguration.class,
|
||||
KubernetesClientAutoConfiguration.class, SimpleReactiveDiscoveryClientAutoConfiguration.class,
|
||||
UtilAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class,
|
||||
KubernetesCommonsAutoConfiguration.class, KubernetesInformerAutoConfiguration.class))
|
||||
KubernetesCommonsAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class))
|
||||
.withPropertyValues(properties);
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ class KubernetesInformerReactiveDiscoveryClientAutoConfigurationApplicationConte
|
||||
KubernetesInformerReactiveDiscoveryClientAutoConfiguration.class,
|
||||
KubernetesClientAutoConfiguration.class, SimpleReactiveDiscoveryClientAutoConfiguration.class,
|
||||
UtilAutoConfiguration.class, KubernetesDiscoveryPropertiesAutoConfiguration.class,
|
||||
KubernetesCommonsAutoConfiguration.class, KubernetesInformerAutoConfiguration.class))
|
||||
KubernetesCommonsAutoConfiguration.class, KubernetesClientInformerAutoConfiguration.class))
|
||||
.withClassLoader(new FilteredClassLoader(name)).withPropertyValues(properties);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.discovery.reactive;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -29,77 +29,97 @@ import io.kubernetes.client.openapi.models.V1EndpointSubset;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceSpec;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceStatus;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
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.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
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class KubernetesInformerReactiveDiscoveryClientTests {
|
||||
class KubernetesInformerReactiveDiscoveryClientTests {
|
||||
|
||||
@Mock
|
||||
private SharedInformerFactory sharedInformerFactory;
|
||||
// default constructor partitions by namespace
|
||||
private Cache<V1Service> serviceCache = new Cache<>();
|
||||
|
||||
private static final V1Service testService1 = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("test-svc-1").namespace("namespace1"))
|
||||
.spec(new V1ServiceSpec().loadBalancerIP("1.1.1.1")).status(new V1ServiceStatus());
|
||||
// default constructor partitions by namespace
|
||||
private Cache<V1Endpoints> endpointsCache = new Cache<>();
|
||||
|
||||
private static final V1Service testService2 = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("test-svc-1").namespace("namespace2"))
|
||||
.spec(new V1ServiceSpec().loadBalancerIP("1.1.1.1")).status(new V1ServiceStatus());
|
||||
private static final String NAMESPACE_1 = "namespace1";
|
||||
|
||||
private static final V1Endpoints testEndpoints1 = new V1Endpoints()
|
||||
.metadata(new V1ObjectMeta().name("test-svc-1").namespace("namespace1"))
|
||||
private static final String NAMESPACE_2 = "namespace2";
|
||||
|
||||
private final SharedInformerFactory sharedInformerFactory = Mockito.mock(SharedInformerFactory.class);
|
||||
|
||||
private static final V1Service TEST_SERVICE_1 = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("test-svc-1").namespace(NAMESPACE_1));
|
||||
|
||||
private static final V1Service TEST_SERVICE_2 = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("test-svc-2").namespace(NAMESPACE_2));
|
||||
|
||||
// same name as TEST_SERVICE_1, to test distinct
|
||||
private static final V1Service TEST_SERVICE_3 = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("test-svc-2").namespace(NAMESPACE_2));
|
||||
|
||||
private static final V1Endpoints TEST_ENDPOINTS_1 = new V1Endpoints()
|
||||
.metadata(new V1ObjectMeta().name("test-svc-1").namespace(NAMESPACE_1))
|
||||
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new CoreV1EndpointPort().port(8080))
|
||||
.addAddressesItem(new V1EndpointAddress().ip("2.2.2.2")));
|
||||
|
||||
@Test
|
||||
public void testDiscoveryGetServicesAllNamespaceShouldWork() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(testService1, testService2);
|
||||
|
||||
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
|
||||
new KubernetesNamespaceProvider(new MockEnvironment()), sharedInformerFactory, serviceLister, null,
|
||||
null, null, KubernetesDiscoveryProperties.DEFAULT);
|
||||
|
||||
StepVerifier.create(discoveryClient.getServices())
|
||||
.expectNext(testService1.getMetadata().getName(), testService2.getMetadata().getName()).expectComplete()
|
||||
.verify();
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
serviceCache = new Cache<>();
|
||||
endpointsCache = new Cache<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiscoveryGetServicesOneNamespaceShouldWork() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(testService1, testService2);
|
||||
void testDiscoveryGetServicesAllNamespaceShouldWork() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(NAMESPACE_ALL, TEST_SERVICE_1, TEST_SERVICE_2,
|
||||
TEST_SERVICE_3);
|
||||
|
||||
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
|
||||
Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, false);
|
||||
|
||||
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
|
||||
when(kubernetesNamespaceProvider.getNamespace()).thenReturn("namespace1");
|
||||
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
|
||||
kubernetesNamespaceProvider, sharedInformerFactory, serviceLister, null, null, null,
|
||||
KubernetesDiscoveryProperties.DEFAULT);
|
||||
new KubernetesNamespaceProvider(new MockEnvironment()), sharedInformerFactory, serviceLister, null,
|
||||
null, null, kubernetesDiscoveryProperties);
|
||||
|
||||
StepVerifier.create(discoveryClient.getServices()).expectNext(testService1.getMetadata().getName())
|
||||
StepVerifier.create(discoveryClient.getServices())
|
||||
.expectNext(TEST_SERVICE_1.getMetadata().getName(), TEST_SERVICE_2.getMetadata().getName())
|
||||
.expectComplete().verify();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiscoveryGetInstanceAllNamespaceShouldWork() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(testService1, testService2);
|
||||
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
|
||||
void testDiscoveryGetServicesOneNamespaceShouldWork() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(NAMESPACE_1, TEST_SERVICE_1, TEST_SERVICE_2);
|
||||
|
||||
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
|
||||
when(kubernetesNamespaceProvider.getNamespace()).thenReturn(NAMESPACE_1);
|
||||
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
|
||||
kubernetesNamespaceProvider, sharedInformerFactory, serviceLister, null, null, null,
|
||||
KubernetesDiscoveryProperties.DEFAULT);
|
||||
|
||||
StepVerifier.create(discoveryClient.getServices()).expectNext(TEST_SERVICE_1.getMetadata().getName())
|
||||
.expectComplete().verify();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDiscoveryGetInstanceAllNamespaceShouldWork() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(NAMESPACE_ALL, TEST_SERVICE_1, TEST_SERVICE_2);
|
||||
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(NAMESPACE_1, TEST_ENDPOINTS_1);
|
||||
|
||||
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true,
|
||||
Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, false);
|
||||
@@ -108,45 +128,213 @@ public class KubernetesInformerReactiveDiscoveryClientTests {
|
||||
new KubernetesNamespaceProvider(new MockEnvironment()), sharedInformerFactory, serviceLister,
|
||||
endpointsLister, null, null, kubernetesDiscoveryProperties);
|
||||
|
||||
StepVerifier
|
||||
.create(discoveryClient.getInstances("test-svc-1")).expectNext(new DefaultKubernetesServiceInstance("",
|
||||
"test-svc-1", "2.2.2.2", 8080, new HashMap<>(), false, "namespace1", null))
|
||||
StepVerifier.create(discoveryClient.getInstances("test-svc-1"))
|
||||
.expectNext(new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080, Map.of(), false,
|
||||
NAMESPACE_1, null))
|
||||
.expectComplete().verify();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiscoveryGetInstanceOneNamespaceShouldWork() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(testService1, testService2);
|
||||
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
|
||||
void testDiscoveryGetInstanceOneNamespaceShouldWork() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(NAMESPACE_1, TEST_SERVICE_1, TEST_SERVICE_2);
|
||||
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(NAMESPACE_1, TEST_ENDPOINTS_1);
|
||||
|
||||
KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false,
|
||||
Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, false);
|
||||
|
||||
KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class);
|
||||
when(kubernetesNamespaceProvider.getNamespace()).thenReturn("namespace1");
|
||||
when(kubernetesNamespaceProvider.getNamespace()).thenReturn(NAMESPACE_1);
|
||||
KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient(
|
||||
kubernetesNamespaceProvider, sharedInformerFactory, serviceLister, endpointsLister, null, null,
|
||||
kubernetesDiscoveryProperties);
|
||||
|
||||
StepVerifier
|
||||
.create(discoveryClient.getInstances("test-svc-1")).expectNext(new DefaultKubernetesServiceInstance("",
|
||||
"test-svc-1", "2.2.2.2", 8080, new HashMap<>(), false, "namespace1", null))
|
||||
StepVerifier.create(discoveryClient.getInstances("test-svc-1"))
|
||||
.expectNext(new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080, Map.of(), false,
|
||||
NAMESPACE_1, null))
|
||||
.expectComplete().verify();
|
||||
|
||||
}
|
||||
|
||||
private Lister<V1Service> setupServiceLister(V1Service... services) {
|
||||
Cache<V1Service> serviceCache = new Cache<>();
|
||||
Lister<V1Service> serviceLister = new Lister<>(serviceCache);
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = true
|
||||
* - service-a in namespace-a exists
|
||||
* - service-b in namespace-b exists
|
||||
*
|
||||
* As such, both services are found.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testAllNamespacesTwoServicesPresent() {
|
||||
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"));
|
||||
serviceCache.add(serviceA);
|
||||
serviceCache.add(serviceB);
|
||||
|
||||
Lister<V1Service> serviceLister = new Lister<>(serviceCache).namespace(NAMESPACE_ALL);
|
||||
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);
|
||||
|
||||
List<String> result = discoveryClient.getServices().collectList().block();
|
||||
Assertions.assertEquals(result.size(), 2);
|
||||
Assertions.assertTrue(result.contains("service-a"));
|
||||
Assertions.assertTrue(result.contains("service-b"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = false
|
||||
* - service-a in namespace-a exists
|
||||
* - service-b in namespace-b exists
|
||||
* - service lister exists in namespace-a
|
||||
*
|
||||
* As such, one service is found.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testSingleNamespaceTwoServicesPresent() {
|
||||
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"));
|
||||
serviceCache.add(serviceA);
|
||||
serviceCache.add(serviceB);
|
||||
|
||||
Lister<V1Service> serviceLister = new Lister<>(serviceCache).namespace("namespace-a");
|
||||
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);
|
||||
|
||||
List<String> result = discoveryClient.getServices().collectList().block();
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertTrue(result.contains("service-a"));
|
||||
Assertions.assertFalse(result.contains("service-b"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = true
|
||||
* - endpoints-X in namespace-a exists
|
||||
* - endpoints-X in namespace-b exists
|
||||
*
|
||||
* As such, both endpoints are found.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testAllNamespacesTwoEndpointsPresent() {
|
||||
boolean allNamespaces = true;
|
||||
|
||||
V1Service serviceXNamespaceA = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-a"));
|
||||
V1Service serviceXNamespaceB = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-b"));
|
||||
serviceCache.add(serviceXNamespaceA);
|
||||
serviceCache.add(serviceXNamespaceB);
|
||||
|
||||
V1Endpoints endpointsXNamespaceA = new V1Endpoints()
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-a"))
|
||||
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new CoreV1EndpointPort().port(8080))
|
||||
.addAddressesItem(new V1EndpointAddress().ip("1.1.1.1")));
|
||||
V1Endpoints endpointsXNamespaceB = new V1Endpoints()
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-b"))
|
||||
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new CoreV1EndpointPort().port(8080))
|
||||
.addAddressesItem(new V1EndpointAddress().ip("2.2.2.2")));
|
||||
endpointsCache.add(endpointsXNamespaceA);
|
||||
endpointsCache.add(endpointsXNamespaceB);
|
||||
|
||||
Lister<V1Endpoints> endpointsLister = new Lister<>(endpointsCache).namespace(NAMESPACE_ALL);
|
||||
Lister<V1Service> serviceLister = new Lister<>(serviceCache).namespace(NAMESPACE_ALL);
|
||||
|
||||
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, endpointsLister, null, null,
|
||||
kubernetesDiscoveryProperties);
|
||||
|
||||
List<ServiceInstance> result = discoveryClient.getInstances("endpoints-x").collectList().block();
|
||||
Assertions.assertEquals(result.size(), 2);
|
||||
List<String> byIp = result.stream().map(ServiceInstance::getHost).sorted().toList();
|
||||
Assertions.assertTrue(byIp.contains("1.1.1.1"));
|
||||
Assertions.assertTrue(byIp.contains("2.2.2.2"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - all-namespaces = true
|
||||
* - endpoints-X in namespace-a exists
|
||||
* - endpoints-X in namespace-b exists
|
||||
*
|
||||
* We search in namespace-a, only. As such, single endpoints is found.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testAllSingleTwoEndpointsPresent() {
|
||||
boolean allNamespaces = true;
|
||||
|
||||
V1Service serviceXNamespaceA = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-a"));
|
||||
V1Service serviceXNamespaceB = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-b"));
|
||||
serviceCache.add(serviceXNamespaceA);
|
||||
serviceCache.add(serviceXNamespaceB);
|
||||
|
||||
V1Endpoints endpointsXNamespaceA = new V1Endpoints()
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-a"))
|
||||
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new CoreV1EndpointPort().port(8080))
|
||||
.addAddressesItem(new V1EndpointAddress().ip("1.1.1.1")));
|
||||
V1Endpoints endpointsXNamespaceB = new V1Endpoints()
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-b"))
|
||||
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new CoreV1EndpointPort().port(8080))
|
||||
.addAddressesItem(new V1EndpointAddress().ip("2.2.2.2")));
|
||||
endpointsCache.add(endpointsXNamespaceA);
|
||||
endpointsCache.add(endpointsXNamespaceB);
|
||||
|
||||
Lister<V1Endpoints> endpointsLister = new Lister<>(endpointsCache).namespace("namespace-a");
|
||||
Lister<V1Service> serviceLister = new Lister<>(serviceCache).namespace("namespace-a");
|
||||
|
||||
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, endpointsLister, null, null,
|
||||
kubernetesDiscoveryProperties);
|
||||
|
||||
List<ServiceInstance> result = discoveryClient.getInstances("endpoints-x").collectList().block();
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
List<String> byIp = result.stream().map(ServiceInstance::getHost).sorted().toList();
|
||||
Assertions.assertTrue(byIp.contains("1.1.1.1"));
|
||||
}
|
||||
|
||||
private Lister<V1Service> setupServiceLister(String namespace, V1Service... services) {
|
||||
Lister<V1Service> serviceLister = new Lister<>(serviceCache, namespace);
|
||||
for (V1Service svc : services) {
|
||||
serviceCache.add(svc);
|
||||
}
|
||||
return serviceLister;
|
||||
}
|
||||
|
||||
private Lister<V1Endpoints> setupEndpointsLister(V1Endpoints... endpoints) {
|
||||
Cache<V1Endpoints> endpointsCache = new Cache<>();
|
||||
private Lister<V1Endpoints> setupEndpointsLister(String namespace, V1Endpoints... endpoints) {
|
||||
Lister<V1Endpoints> endpointsLister = new Lister<>(endpointsCache);
|
||||
for (V1Endpoints ep : endpoints) {
|
||||
endpointsCache.add(ep);
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
<module>spring-cloud-kubernetes-client-secrets-event-reload-multiple-apps</module>
|
||||
<module>spring-cloud-kubernetes-fabric8-client-catalog-watcher</module>
|
||||
<module>spring-cloud-kubernetes-client-catalog-watcher</module>
|
||||
<module>spring-cloud-kubernetes-client-discovery-it</module>
|
||||
<module>spring-cloud-kubernetes-fabric8-client-discovery-with-bootstrap</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
||||
@@ -85,6 +85,7 @@ public class KubernetesClientCatalogWatchNamespacesIT {
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
util.deleteClusterWide(NAMESPACE_DEFAULT, Set.of(NAMESPACE_A, NAMESPACE_B));
|
||||
util.deleteNamespace(NAMESPACE_A);
|
||||
util.deleteNamespace(NAMESPACE_B);
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ class ConfigMapEventReloadIT {
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() throws Exception {
|
||||
util.deleteClusterWide(NAMESPACE, Set.of("left", "right"));
|
||||
util.deleteNamespace("left");
|
||||
util.deleteNamespace("right");
|
||||
Commons.cleanUp(IMAGE_NAME, K3S);
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-client-discovery-it</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-client-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-transport-httpclient5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>../src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<!-- build image in the 'package' phase, and ignore plain tests -->
|
||||
<!-- via maven-surefire-plugin::skipTests -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-image</id>
|
||||
<configuration>
|
||||
<skip>${skip.build.image}</skip>
|
||||
</configuration>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>repackage</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- run tests in the 'integration-tests' phase, one that is after 'package' (where we build the image) -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>imagename</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!env.IMAGE</name>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<env.IMAGE>springcloud/${project.artifactId}:${project.version}</env.IMAGE>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class DiscoveryApp {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DiscoveryApp.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.util.List;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.client.discovery.KubernetesInformerDiscoveryClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@RestController
|
||||
public class DiscoveryController {
|
||||
|
||||
private final KubernetesInformerDiscoveryClient discoveryClient;
|
||||
|
||||
public DiscoveryController(KubernetesInformerDiscoveryClient discoveryClient) {
|
||||
this.discoveryClient = discoveryClient;
|
||||
}
|
||||
|
||||
@GetMapping("/services")
|
||||
public List<String> allServices() {
|
||||
return discoveryClient.getServices();
|
||||
}
|
||||
|
||||
@GetMapping("/service-instances/{serviceId}")
|
||||
public List<ServiceInstance> serviceInstances(@PathVariable("serviceId") String serviceId) {
|
||||
return discoveryClient.getInstances(serviceId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* 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.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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 KubernetesClientDiscoveryClientIT {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void after() throws Exception {
|
||||
Commons.cleanUp(IMAGE_NAME, K3S);
|
||||
}
|
||||
|
||||
/**
|
||||
* Three services are deployed in the default namespace. We do not configure any
|
||||
* explicit namespace and 'default' must be picked-up.
|
||||
*/
|
||||
@Test
|
||||
void testSimple() {
|
||||
|
||||
// set-up
|
||||
util.setUp(NAMESPACE);
|
||||
manifests(false, null, Phase.CREATE);
|
||||
util.busybox(NAMESPACE, Phase.CREATE);
|
||||
|
||||
assertLogStatement("serviceSharedInformer will use namespace : default");
|
||||
|
||||
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(), 3);
|
||||
Assertions.assertTrue(servicesResult.contains("kubernetes"));
|
||||
Assertions.assertTrue(servicesResult.contains("spring-cloud-kubernetes-client-discovery-it"));
|
||||
Assertions.assertTrue(servicesResult.contains("busybox-service"));
|
||||
|
||||
WebClient ourServiceClient = builder()
|
||||
.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>>() {
|
||||
|
||||
}).retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertEquals(ourServiceInstances.size(), 1);
|
||||
|
||||
DefaultKubernetesServiceInstance serviceInstance = ourServiceInstances.get(0);
|
||||
Assertions.assertNotNull(serviceInstance.getInstanceId());
|
||||
Assertions.assertEquals(serviceInstance.getServiceId(), "spring-cloud-kubernetes-client-discovery-it");
|
||||
Assertions.assertNotNull(serviceInstance.getHost());
|
||||
Assertions.assertEquals(serviceInstance.getMetadata(),
|
||||
Map.of("http", "8080", "app", "spring-cloud-kubernetes-client-discovery-it"));
|
||||
Assertions.assertEquals(serviceInstance.getPort(), 8080);
|
||||
Assertions.assertEquals(serviceInstance.getNamespace(), "default");
|
||||
|
||||
WebClient busyBoxServiceClient = builder().baseUrl("http://localhost//service-instances/busybox-service")
|
||||
.build();
|
||||
List<DefaultKubernetesServiceInstance> busyBoxServiceInstances = busyBoxServiceClient.method(HttpMethod.GET)
|
||||
.retrieve().bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
|
||||
|
||||
}).retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertEquals(busyBoxServiceInstances.size(), 2);
|
||||
|
||||
// clean-up
|
||||
util.busybox(NAMESPACE, Phase.DELETE);
|
||||
manifests(false, null, Phase.DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - config server is enabled for all namespaces
|
||||
* - wiremock service is deployed in namespace-a
|
||||
* - busybox service is deployed in namespace-b
|
||||
*
|
||||
* Our discovery searches in all namespaces, thus finds them both.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testAllNamespaces() {
|
||||
util.createNamespace(NAMESPACE_A);
|
||||
util.createNamespace(NAMESPACE_B);
|
||||
util.setUpClusterWideClusterRoleBinding(NAMESPACE);
|
||||
util.wiremock(NAMESPACE_A, "/wiremock", Phase.CREATE);
|
||||
util.busybox(NAMESPACE_B, Phase.CREATE);
|
||||
manifests(true, null, Phase.CREATE);
|
||||
|
||||
assertLogStatement("serviceSharedInformer will use all-namespaces");
|
||||
|
||||
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(), 7);
|
||||
Assertions.assertTrue(servicesResult.contains("kubernetes"));
|
||||
Assertions.assertTrue(servicesResult.contains("spring-cloud-kubernetes-client-discovery-it"));
|
||||
Assertions.assertTrue(servicesResult.contains("busybox-service"));
|
||||
Assertions.assertTrue(servicesResult.contains("service-wiremock"));
|
||||
|
||||
manifests(true, null, Phase.DELETE);
|
||||
util.wiremock(NAMESPACE_A, "/wiremock", Phase.DELETE);
|
||||
util.busybox(NAMESPACE_B, Phase.DELETE);
|
||||
util.deleteClusterWideClusterRoleBinding(NAMESPACE);
|
||||
util.deleteNamespace(NAMESPACE_A);
|
||||
util.deleteNamespace(NAMESPACE_B);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - config server is enabled for namespace-a
|
||||
* - wiremock service is deployed in namespace-a
|
||||
* - wiremock service is deployed in namespace-b
|
||||
*
|
||||
* Only service in namespace-a is found.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testSpecificNamespace() {
|
||||
util.createNamespace(NAMESPACE_A);
|
||||
util.createNamespace(NAMESPACE_B);
|
||||
util.setUpClusterWide(NAMESPACE, Set.of(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");
|
||||
|
||||
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 wiremockInNamespaceAClient = builder().baseUrl("http://localhost//service-instances/service-wiremock")
|
||||
.build();
|
||||
|
||||
List<DefaultKubernetesServiceInstance> wiremockInNamespaceA = wiremockInNamespaceAClient.method(HttpMethod.GET)
|
||||
.retrieve().bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
|
||||
|
||||
}).retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertEquals(wiremockInNamespaceA.size(), 1);
|
||||
|
||||
DefaultKubernetesServiceInstance serviceInstance = wiremockInNamespaceA.get(0);
|
||||
Assertions.assertEquals(serviceInstance.getNamespace(), "a");
|
||||
|
||||
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.deleteNamespace(NAMESPACE_A);
|
||||
util.deleteNamespace(NAMESPACE_B);
|
||||
}
|
||||
|
||||
private static void manifests(boolean allNamespaces, String clientSpecificNamespace, Phase phase) {
|
||||
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");
|
||||
if (allNamespaces) {
|
||||
V1EnvVar allNamespacesVar = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_DISCOVERY_ALL_NAMESPACES")
|
||||
.value("TRUE");
|
||||
envVars.add(allNamespacesVar);
|
||||
}
|
||||
|
||||
if (clientSpecificNamespace != null) {
|
||||
V1EnvVar clientNamespace = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_CLIENT_NAMESPACE")
|
||||
.value(NAMESPACE_A);
|
||||
envVars.add(clientNamespace);
|
||||
}
|
||||
envVars.add(debugLevel);
|
||||
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 WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
private void assertLogStatement(String message) {
|
||||
try {
|
||||
String appPodName = K3S.execInContainer("sh", "-c",
|
||||
"kubectl get pods -l app=" + IMAGE_NAME + " -o=name --no-headers | tr -d '\n'").getStdout();
|
||||
|
||||
Container.ExecResult execResult = K3S.execInContainer("sh", "-c", "kubectl logs " + appPodName.trim());
|
||||
String ok = execResult.getStdout();
|
||||
Assertions.assertTrue(ok.contains(message));
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void assertServicePresentInNamespaces(List<String> namespaces, String value, String serviceName) {
|
||||
namespaces.forEach(x -> {
|
||||
try {
|
||||
String service = K3S.execInContainer("sh", "-c",
|
||||
"kubectl get services -n " + x + " -l app=" + value + " -o=name --no-headers | tr -d '\n'")
|
||||
.getStdout();
|
||||
Assertions.assertEquals(service, "service/" + serviceName);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-client-discovery-deployment-it
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: spring-cloud-kubernetes-client-discovery-it
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-client-discovery-it
|
||||
spec:
|
||||
serviceAccountName: spring-cloud-kubernetes-serviceaccount
|
||||
containers:
|
||||
- name: spring-cloud-kubernetes-client-discovery
|
||||
image: docker.io/springcloud/spring-cloud-kubernetes-client-discovery-it
|
||||
imagePullPolicy: IfNotPresent
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
path: /actuator/health/readiness
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
path: /actuator/health/liveness
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-client-discovery-ingress-it
|
||||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: spring-cloud-kubernetes-client-discovery-it
|
||||
port:
|
||||
number: 8080
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-client-discovery-it
|
||||
name: spring-cloud-kubernetes-client-discovery-it
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: spring-cloud-kubernetes-client-discovery-it
|
||||
type: ClusterIP
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="INFO"/>
|
||||
</configuration>
|
||||
@@ -77,6 +77,7 @@ class ActuatorRefreshMultipleNamespacesIT {
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() throws Exception {
|
||||
util.deleteClusterWide(DEFAULT_NAMESPACE, Set.of(DEFAULT_NAMESPACE, LEFT_NAMESPACE, RIGHT_NAMESPACE));
|
||||
util.deleteNamespace(LEFT_NAMESPACE);
|
||||
util.deleteNamespace(RIGHT_NAMESPACE);
|
||||
Commons.cleanUp(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.configmap;
|
||||
package org.springframework.cloud.kubernetes.fabric8.discovery;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@@ -14,14 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.configmap;
|
||||
package org.springframework.cloud.kubernetes.fabric8.discovery;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Endpoints;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.fabric8.discovery.KubernetesDiscoveryClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.configmap;
|
||||
package org.springframework.cloud.kubernetes.fabric8.discovery;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.configmap;
|
||||
package org.springframework.cloud.kubernetes.fabric8.discovery;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.configmap;
|
||||
package org.springframework.cloud.kubernetes.fabric8.discovery;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.configmap;
|
||||
package org.springframework.cloud.kubernetes.fabric8.discovery;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
@@ -36,6 +36,7 @@ import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
|
||||
import io.kubernetes.client.openapi.apis.RbacAuthorizationV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1ClusterRole;
|
||||
import io.kubernetes.client.openapi.models.V1ClusterRoleBinding;
|
||||
import io.kubernetes.client.openapi.models.V1ConfigMap;
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1DeploymentList;
|
||||
@@ -131,6 +132,7 @@ public final class Util {
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
@@ -298,6 +300,49 @@ public final class Util {
|
||||
|
||||
}
|
||||
|
||||
public void setUpClusterWideClusterRoleBinding(String serviceAccountNamespace) {
|
||||
|
||||
try {
|
||||
V1ServiceAccount serviceAccount = (V1ServiceAccount) yaml("cluster/service-account.yaml");
|
||||
CheckedSupplier<V1ServiceAccount> accountSupplier = () -> coreV1Api.readNamespacedServiceAccount(
|
||||
serviceAccount.getMetadata().getName(), serviceAccountNamespace, null);
|
||||
CheckedSupplier<V1ServiceAccount> accountDefaulter = () -> coreV1Api
|
||||
.createNamespacedServiceAccount(serviceAccountNamespace, serviceAccount, null, null, null, null);
|
||||
notExistsHandler(accountSupplier, accountDefaulter);
|
||||
|
||||
V1ClusterRole clusterRole = (V1ClusterRole) yaml("cluster/cluster-role.yaml");
|
||||
notExistsHandler(() -> rbacApi.readClusterRole(clusterRole.getMetadata().getName(), null),
|
||||
() -> rbacApi.createClusterRole(clusterRole, null, null, null, null));
|
||||
|
||||
V1ClusterRoleBinding clusterRoleBinding = (V1ClusterRoleBinding) yaml("cluster/cluster-role-binding.yaml");
|
||||
notExistsHandler(() -> rbacApi.readClusterRoleBinding(clusterRoleBinding.getMetadata().getName(), null),
|
||||
() -> rbacApi.createClusterRoleBinding(clusterRoleBinding, null, null, null, null));
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void deleteClusterWideClusterRoleBinding(String serviceAccountNamespace) {
|
||||
try {
|
||||
V1ServiceAccount serviceAccount = (V1ServiceAccount) yaml("cluster/service-account.yaml");
|
||||
V1ClusterRole clusterRole = (V1ClusterRole) yaml("cluster/cluster-role.yaml");
|
||||
V1ClusterRoleBinding clusterRoleBinding = (V1ClusterRoleBinding) yaml("cluster/cluster-role-binding.yaml");
|
||||
|
||||
coreV1Api.deleteNamespacedServiceAccount(serviceAccount.getMetadata().getName(), serviceAccountNamespace,
|
||||
null, null, null, null, null, null);
|
||||
rbacApi.deleteClusterRole(clusterRole.getMetadata().getName(), null, null, null, null, null, null);
|
||||
rbacApi.deleteClusterRoleBinding(clusterRoleBinding.getMetadata().getName(), null, null, null, null, null,
|
||||
null);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setUpClusterWide(String serviceAccountNamespace, Set<String> namespaces) {
|
||||
|
||||
try {
|
||||
@@ -332,6 +377,31 @@ public final class Util {
|
||||
|
||||
}
|
||||
|
||||
public void deleteClusterWide(String serviceAccountNamespace, Set<String> namespaces) {
|
||||
try {
|
||||
V1ServiceAccount serviceAccount = (V1ServiceAccount) yaml("cluster/service-account.yaml");
|
||||
V1ClusterRole clusterRole = (V1ClusterRole) yaml("cluster/cluster-role.yaml");
|
||||
V1RoleBinding roleBinding = (V1RoleBinding) yaml("cluster/role-binding.yaml");
|
||||
|
||||
coreV1Api.deleteNamespacedServiceAccount(serviceAccount.getMetadata().getName(), serviceAccountNamespace,
|
||||
null, null, null, null, null, null);
|
||||
rbacApi.deleteClusterRole(clusterRole.getMetadata().getName(), null, null, null, null, null, null);
|
||||
namespaces.forEach(namespace -> {
|
||||
roleBinding.getMetadata().setNamespace(namespace);
|
||||
try {
|
||||
rbacApi.deleteNamespacedRoleBinding(roleBinding.getMetadata().getName(), namespace, null, null,
|
||||
null, null, null, null);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteNamespace(String name) {
|
||||
try {
|
||||
coreV1Api.deleteNamespace(name, null, null, null, null, null, null);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-core-k8s-client-it
|
||||
name: spring-cloud-kubernetes-cluster-role-binding
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
name: cluster-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: spring-cloud-kubernetes-serviceaccount
|
||||
namespace: default
|
||||
Reference in New Issue
Block a user