Merge branch 'informer-based-discovery-client' of https://github.com/yue9944882/spring-cloud-kubernetes into yue9944882-informer-based-discovery-client
This commit is contained in:
1
pom.xml
1
pom.xml
@@ -106,6 +106,7 @@
|
||||
<module>docs</module>
|
||||
<module>spring-cloud-kubernetes-loadbalancer</module>
|
||||
<module>spring-cloud-starter-kubernetes-loadbalancer</module>
|
||||
<module>spring-cloud-kubernetes-client-discovery</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java-extended</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java-spring-integration</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
|
||||
|
||||
@@ -46,10 +46,16 @@ public class KubernetesClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CoreV1Api coreApi() throws IOException {
|
||||
public ApiClient apiClient() throws IOException {
|
||||
ApiClient apiClient = kubernetesApiClient();
|
||||
io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient);
|
||||
return new CoreV1Api();
|
||||
return apiClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CoreV1Api coreApi(ApiClient apiClient) throws IOException {
|
||||
return new CoreV1Api(apiClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
61
spring-cloud-kubernetes-client-discovery/pom.xml
Normal file
61
spring-cloud-kubernetes-client-discovery/pom.xml
Normal file
@@ -0,0 +1,61 @@
|
||||
<?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</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-client-discovery</artifactId>
|
||||
<name>Spring Cloud Kubernetes :: Kubernetes Client Discovery</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-client-autoconfig</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
<version>${spring-cloud-commons.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-actuator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-config-client</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2019-2019 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.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@ConditionalOnProperty(value = "spring.cloud.kubernetes.discovery.enabled", matchIfMissing = true)
|
||||
public @interface ConditionalOnKubernetesDiscoveryEnabled {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty("spring.cloud.config.discovery.enabled")
|
||||
@Import({ KubernetesClientAutoConfiguration.class, KubernetesReactiveDiscoveryClientAutoConfiguration.class })
|
||||
public class KubernetesDiscoveryClientConfigClientBootstrapConfiguration {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
@ConfigurationProperties("spring.cloud.kubernetes.discovery")
|
||||
public class KubernetesDiscoveryProperties {
|
||||
|
||||
/** If Kubernetes Discovery is enabled. */
|
||||
private boolean enabled = true;
|
||||
|
||||
/** The service name of the local instance. */
|
||||
@Value("${spring.application.name:unknown}")
|
||||
private String serviceName = "unknown";
|
||||
|
||||
/** If discovering all namespaces. */
|
||||
private boolean allNamespaces = false;
|
||||
|
||||
/*
|
||||
* If wait for the discovery cache (service and endpoints) to be fully loaded,
|
||||
* otherwise aborts the application on starting.
|
||||
*/
|
||||
private boolean waitCacheReady = true;
|
||||
|
||||
/** Timeout for initializing discovery cache, will abort the application if exceeded. **/
|
||||
private long cacheLoadingTimeoutSeconds = 60;
|
||||
|
||||
/**
|
||||
* SpEL expression to filter services AFTER they have been retrieved from the
|
||||
* Kubernetes API server.
|
||||
*/
|
||||
private String filter;
|
||||
|
||||
/** Set the port numbers that are considered secure and use HTTPS. */
|
||||
private Set<Integer> knownSecurePorts = new HashSet<Integer>() {
|
||||
{
|
||||
add(443);
|
||||
add(8443);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If set, then only the services matching these labels will be fetched from the
|
||||
* Kubernetes API server.
|
||||
*/
|
||||
private Map<String, String> serviceLabels = new HashMap<>();
|
||||
|
||||
/**
|
||||
* If set then the port with a given name is used as primary when multiple ports are
|
||||
* defined for a service.
|
||||
*/
|
||||
private String primaryPortName;
|
||||
|
||||
private Metadata metadata = new Metadata();
|
||||
|
||||
private int order = DiscoveryClient.DEFAULT_ORDER;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getServiceName() {
|
||||
return this.serviceName;
|
||||
}
|
||||
|
||||
public void setServiceName(String serviceName) {
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
public String getFilter() {
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
public void setFilter(String filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public Set<Integer> getKnownSecurePorts() {
|
||||
return this.knownSecurePorts;
|
||||
}
|
||||
|
||||
public void setKnownSecurePorts(Set<Integer> knownSecurePorts) {
|
||||
this.knownSecurePorts = knownSecurePorts;
|
||||
}
|
||||
|
||||
public Map<String, String> getServiceLabels() {
|
||||
return this.serviceLabels;
|
||||
}
|
||||
|
||||
public void setServiceLabels(Map<String, String> serviceLabels) {
|
||||
this.serviceLabels = serviceLabels;
|
||||
}
|
||||
|
||||
public String getPrimaryPortName() {
|
||||
return primaryPortName;
|
||||
}
|
||||
|
||||
public void setPrimaryPortName(String primaryPortName) {
|
||||
this.primaryPortName = primaryPortName;
|
||||
}
|
||||
|
||||
public Metadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(Metadata metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public boolean isAllNamespaces() {
|
||||
return allNamespaces;
|
||||
}
|
||||
|
||||
public void setAllNamespaces(boolean allNamespaces) {
|
||||
this.allNamespaces = allNamespaces;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
boolean isWaitCacheReady() {
|
||||
return waitCacheReady;
|
||||
}
|
||||
|
||||
void setWaitCacheReady(boolean waitCacheReady) {
|
||||
this.waitCacheReady = waitCacheReady;
|
||||
}
|
||||
|
||||
long getCacheLoadingTimeoutSeconds() {
|
||||
return cacheLoadingTimeoutSeconds;
|
||||
}
|
||||
|
||||
void setCacheLoadingTimeoutSeconds(long cacheLoadingTimeoutSeconds) {
|
||||
this.cacheLoadingTimeoutSeconds = cacheLoadingTimeoutSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("enabled", this.enabled).append("serviceName", this.serviceName)
|
||||
.append("filter", this.filter).append("knownSecurePorts", this.knownSecurePorts)
|
||||
.append("serviceLabels", this.serviceLabels).append("metadata", this.metadata).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata properties.
|
||||
*/
|
||||
public class Metadata {
|
||||
|
||||
/**
|
||||
* When set, the Kubernetes labels of the services will be included as metadata of
|
||||
* the returned ServiceInstance.
|
||||
*/
|
||||
private boolean addLabels = true;
|
||||
|
||||
/**
|
||||
* When addLabels is set, then this will be used as a prefix to the key names in
|
||||
* the metadata map.
|
||||
*/
|
||||
private String labelsPrefix;
|
||||
|
||||
/**
|
||||
* When set, the Kubernetes annotations of the services will be included as
|
||||
* metadata of the returned ServiceInstance.
|
||||
*/
|
||||
private boolean addAnnotations = true;
|
||||
|
||||
/**
|
||||
* When addAnnotations is set, then this will be used as a prefix to the key names
|
||||
* in the metadata map.
|
||||
*/
|
||||
private String annotationsPrefix;
|
||||
|
||||
/**
|
||||
* When set, any named Kubernetes service ports will be included as metadata of
|
||||
* the returned ServiceInstance.
|
||||
*/
|
||||
private boolean addPorts = true;
|
||||
|
||||
/**
|
||||
* When addPorts is set, then this will be used as a prefix to the key names in
|
||||
* the metadata map.
|
||||
*/
|
||||
private String portsPrefix = "port.";
|
||||
|
||||
public boolean isAddLabels() {
|
||||
return this.addLabels;
|
||||
}
|
||||
|
||||
public void setAddLabels(boolean addLabels) {
|
||||
this.addLabels = addLabels;
|
||||
}
|
||||
|
||||
public String getLabelsPrefix() {
|
||||
return this.labelsPrefix;
|
||||
}
|
||||
|
||||
public void setLabelsPrefix(String labelsPrefix) {
|
||||
this.labelsPrefix = labelsPrefix;
|
||||
}
|
||||
|
||||
public boolean isAddAnnotations() {
|
||||
return this.addAnnotations;
|
||||
}
|
||||
|
||||
public void setAddAnnotations(boolean addAnnotations) {
|
||||
this.addAnnotations = addAnnotations;
|
||||
}
|
||||
|
||||
public String getAnnotationsPrefix() {
|
||||
return this.annotationsPrefix;
|
||||
}
|
||||
|
||||
public void setAnnotationsPrefix(String annotationsPrefix) {
|
||||
this.annotationsPrefix = annotationsPrefix;
|
||||
}
|
||||
|
||||
public boolean isAddPorts() {
|
||||
return this.addPorts;
|
||||
}
|
||||
|
||||
public void setAddPorts(boolean addPorts) {
|
||||
this.addPorts = addPorts;
|
||||
}
|
||||
|
||||
public String getPortsPrefix() {
|
||||
return this.portsPrefix;
|
||||
}
|
||||
|
||||
public void setPortsPrefix(String portsPrefix) {
|
||||
this.portsPrefix = portsPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("addLabels", this.addLabels)
|
||||
.append("labelsPrefix", this.labelsPrefix).append("addAnnotations", this.addAnnotations)
|
||||
.append("annotationsPrefix", this.annotationsPrefix).append("addPorts", this.addPorts)
|
||||
.append("portsPrefix", this.portsPrefix).toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.kubernetes.client.extended.wait.Wait;
|
||||
import io.kubernetes.client.informer.SharedInformer;
|
||||
import io.kubernetes.client.informer.SharedInformerFactory;
|
||||
import io.kubernetes.client.informer.cache.Lister;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointPort;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class KubernetesInformerDiscoveryClient implements DiscoveryClient, InitializingBean {
|
||||
|
||||
private static final Log log = LogFactory.getLog(KubernetesInformerDiscoveryClient.class);
|
||||
|
||||
private final SharedInformerFactory sharedInformerFactory;
|
||||
|
||||
private final Lister<V1Service> serviceLister;
|
||||
|
||||
private final Supplier<Boolean> informersReadyFunc;
|
||||
|
||||
private final Lister<V1Endpoints> endpointsLister;
|
||||
|
||||
private final KubernetesDiscoveryProperties properties;
|
||||
|
||||
private final String namespace;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Kubernetes Client Discovery";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ServiceInstance> getInstances(String serviceId) {
|
||||
Assert.notNull(serviceId, "[Assertion failed] - the object argument must not be null");
|
||||
|
||||
V1Service service = properties.isAllNamespaces() ? this.serviceLister.list().stream()
|
||||
.filter(svc -> serviceId.equals(svc.getMetadata().getName())).findFirst().orElse(null)
|
||||
: this.serviceLister.namespace(this.namespace).get(serviceId);
|
||||
if (service == null) {
|
||||
// no such service present in the cluster
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
Map<String, String> svcMetadata = new HashMap<>();
|
||||
if (this.properties.getMetadata() != null) {
|
||||
if (this.properties.getMetadata().isAddLabels()) {
|
||||
if (service.getMetadata().getLabels() != null) {
|
||||
String labelPrefix = this.properties.getMetadata().getLabelsPrefix() != null
|
||||
? this.properties.getMetadata().getLabelsPrefix() : "";
|
||||
service.getMetadata().getLabels().entrySet().stream()
|
||||
.filter(e -> e.getKey().startsWith(labelPrefix))
|
||||
.forEach(e -> svcMetadata.put(e.getKey(), e.getValue()));
|
||||
}
|
||||
}
|
||||
if (this.properties.getMetadata().isAddAnnotations()) {
|
||||
if (service.getMetadata().getAnnotations() != null) {
|
||||
String annotationPrefix = this.properties.getMetadata().getAnnotationsPrefix() != null
|
||||
? this.properties.getMetadata().getAnnotationsPrefix() : "";
|
||||
service.getMetadata().getAnnotations().entrySet().stream()
|
||||
.filter(e -> e.getKey().startsWith(annotationPrefix))
|
||||
.forEach(e -> svcMetadata.put(e.getKey(), e.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
V1Endpoints ep = this.endpointsLister.namespace(service.getMetadata().getNamespace())
|
||||
.get(service.getMetadata().getName());
|
||||
if (ep == null) {
|
||||
// no available endpoints in the cluster
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return ep.getSubsets().stream().flatMap(subset -> {
|
||||
Map<String, String> metadata = new HashMap<>(svcMetadata);
|
||||
if (this.properties.getMetadata() != null && this.properties.getMetadata().isAddPorts()) {
|
||||
subset.getPorts().stream().forEach(p -> metadata.put(p.getName(), Integer.toString(p.getPort())));
|
||||
}
|
||||
V1EndpointPort port = subset.getPorts() != null && subset.getPorts().size() == 1 ? subset.getPorts().get(0)
|
||||
: subset.getPorts().stream()
|
||||
.filter(p -> this.properties.getPrimaryPortName().equalsIgnoreCase(p.getName())).findFirst()
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
return subset.getAddresses().stream()
|
||||
.map(addr -> new KubernetesServiceInstance(
|
||||
addr.getTargetRef() != null ? addr.getTargetRef().getUid() : "", serviceId, addr.getIp(),
|
||||
port.getPort(), metadata, false));
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getServices() {
|
||||
List<V1Service> services = this.properties.isAllNamespaces() ? this.serviceLister.list()
|
||||
: this.serviceLister.namespace(this.namespace).list();
|
||||
return services.stream().map(s -> s.getMetadata().getName()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
this.sharedInformerFactory.startAllRegisteredInformers();
|
||||
if (!Wait.poll(Duration.ofSeconds(1), Duration
|
||||
.ofSeconds(this.properties.getCacheLoadingTimeoutSeconds()), () -> {
|
||||
log.info("Waiting for the cache of informers to be fully loaded..");
|
||||
return this.informersReadyFunc.get();
|
||||
})) {
|
||||
if (this.properties.isWaitCacheReady()) {
|
||||
throw new IllegalStateException(
|
||||
"Timeout waiting for informers cache to be ready, is the kubernetes service up?");
|
||||
}
|
||||
else {
|
||||
log.warn("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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.SharedInformer;
|
||||
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.spring.extended.controller.KubernetesInformerFactoryProcessor;
|
||||
import io.kubernetes.client.spring.extended.controller.annotation.GroupVersionResource;
|
||||
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformer;
|
||||
import io.kubernetes.client.spring.extended.controller.annotation.KubernetesInformers;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.client.CommonsClientAutoConfiguration;
|
||||
import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled;
|
||||
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.client.discovery.gson.EndpointsTrimmingStrategy;
|
||||
import org.springframework.cloud.kubernetes.client.discovery.gson.ServiceTrimmingStrategy;
|
||||
import org.springframework.cloud.kubernetes.commons.KubernetesClientProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnKubernetesDiscoveryEnabled
|
||||
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class })
|
||||
@AutoConfigureAfter({ KubernetesClientAutoConfiguration.class })
|
||||
public class KubernetesReactiveDiscoveryClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public KubernetesDiscoveryProperties getKubernetesDiscoveryProperties() {
|
||||
return new KubernetesDiscoveryProperties();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnBlockingDiscoveryEnabled
|
||||
public static class KubernetesInformerDiscoveryConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public KubernetesInformerFactoryProcessor kubernetesInformerFactoryProcessor() {
|
||||
return new KubernetesInformerFactoryProcessor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CatalogSharedInformerFactory catalogSharedInformerFactory(ApiClient apiClient) {
|
||||
apiClient.getJSON()
|
||||
.setGson(apiClient.getJSON().getGson().newBuilder()
|
||||
.addDeserializationExclusionStrategy(new ServiceTrimmingStrategy())
|
||||
.addDeserializationExclusionStrategy(new EndpointsTrimmingStrategy()).create());
|
||||
return new CatalogSharedInformerFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public KubernetesInformerDiscoveryClient kubernetesInformerDiscoveryClient(
|
||||
KubernetesClientProperties kubernetesClientProperties,
|
||||
CatalogSharedInformerFactory sharedInformerFactory, Lister<V1Service> serviceLister,
|
||||
Lister<V1Endpoints> endpointsLister, SharedInformer<V1Service> serviceInformer,
|
||||
SharedInformer<V1Endpoints> endpointsInformer, KubernetesDiscoveryProperties properties) {
|
||||
return new KubernetesInformerDiscoveryClient(kubernetesClientProperties.getNamespace(),
|
||||
sharedInformerFactory, serviceLister, endpointsLister, serviceInformer, endpointsInformer,
|
||||
properties);
|
||||
}
|
||||
|
||||
@KubernetesInformers({
|
||||
@KubernetesInformer(apiTypeClass = V1Service.class, apiListTypeClass = V1ServiceList.class,
|
||||
groupVersionResource = @GroupVersionResource(apiGroup = "", apiVersion = "v1",
|
||||
resourcePlural = "services")),
|
||||
@KubernetesInformer(apiTypeClass = V1Endpoints.class, apiListTypeClass = V1EndpointsList.class,
|
||||
groupVersionResource = @GroupVersionResource(apiGroup = "", apiVersion = "v1",
|
||||
resourcePlural = "endpoints")) })
|
||||
class CatalogSharedInformerFactory extends SharedInformerFactory {
|
||||
|
||||
// TODO: optimization to ease memory pressure from continuous list&watch.
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
|
||||
public class KubernetesServiceInstance implements ServiceInstance {
|
||||
|
||||
/**
|
||||
* Key of the namespace metadata.
|
||||
*/
|
||||
public static final String NAMESPACE_METADATA_KEY = "k8s_namespace";
|
||||
|
||||
private static final String HTTP_PREFIX = "http";
|
||||
|
||||
private static final String HTTPS_PREFIX = "https";
|
||||
|
||||
private static final String DSL = "//";
|
||||
|
||||
private static final String COLON = ":";
|
||||
|
||||
private final String instanceId;
|
||||
|
||||
private final String serviceId;
|
||||
|
||||
private final String host;
|
||||
|
||||
private final int port;
|
||||
|
||||
private final URI uri;
|
||||
|
||||
private final Boolean secure;
|
||||
|
||||
private final Map<String, String> metadata;
|
||||
|
||||
/**
|
||||
* @param instanceId the id of the instance.
|
||||
* @param serviceId the id of the service.
|
||||
* @param host the address where the service instance can be found.
|
||||
* @param port the port on which the service is running.
|
||||
* @param metadata a map containing metadata.
|
||||
* @param secure indicates whether or not the connection needs to be secure.
|
||||
*/
|
||||
public KubernetesServiceInstance(String instanceId, String serviceId, String host, int port,
|
||||
Map<String, String> metadata, Boolean secure) {
|
||||
this.instanceId = instanceId;
|
||||
this.serviceId = serviceId;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.metadata = metadata;
|
||||
this.secure = secure;
|
||||
this.uri = createUri(secure ? HTTPS_PREFIX : HTTP_PREFIX, host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInstanceId() {
|
||||
return this.instanceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceId() {
|
||||
return this.serviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost() {
|
||||
return this.host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return this.secure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public Map<String, String> getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getScheme() {
|
||||
return isSecure() ? HTTPS_PREFIX : HTTP_PREFIX;
|
||||
}
|
||||
|
||||
private URI createUri(String scheme, String host, int port) {
|
||||
return URI.create(scheme + COLON + DSL + host + COLON + port);
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return this.metadata != null ? this.metadata.get(NAMESPACE_METADATA_KEY) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
KubernetesServiceInstance that = (KubernetesServiceInstance) o;
|
||||
return port == that.port && Objects.equals(instanceId, that.instanceId)
|
||||
&& Objects.equals(serviceId, that.serviceId) && Objects.equals(host, that.host)
|
||||
&& Objects.equals(uri, that.uri) && Objects.equals(secure, that.secure)
|
||||
&& Objects.equals(metadata, that.metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(instanceId, serviceId, host, port, uri, secure, metadata);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.gson;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.FieldAttributes;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
|
||||
public class EndpointsTrimmingStrategy implements ExclusionStrategy {
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
|
||||
// trimming field-managers
|
||||
if (V1ObjectMeta.class.equals(fieldAttributes.getDeclaringClass())) {
|
||||
return "managedFields".equals(fieldAttributes.getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(Class<?> aClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.gson;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.FieldAttributes;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceSpec;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceStatus;
|
||||
|
||||
public class ServiceTrimmingStrategy implements ExclusionStrategy {
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
|
||||
// trimming field-managers
|
||||
if (V1ObjectMeta.class.equals(fieldAttributes.getDeclaringClass())) {
|
||||
return "managedFields".equals(fieldAttributes.getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(Class<?> aClass) {
|
||||
if (V1ServiceSpec.class.equals(aClass)) {
|
||||
return true;
|
||||
}
|
||||
return V1ServiceStatus.class.equals(aClass);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.kubernetes.client.discovery.KubernetesReactiveDiscoveryClientAutoConfiguration
|
||||
|
||||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
|
||||
org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientConfigClientBootstrapConfiguration
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.Collections;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.commons.util.UtilAutoConfiguration;
|
||||
import org.springframework.cloud.config.client.ConfigClientProperties;
|
||||
import org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration;
|
||||
import org.springframework.cloud.kubernetes.client.KubernetesClientAutoConfiguration;
|
||||
import org.springframework.cloud.kubernetes.commons.KubernetesCommonsAutoConfiguration;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty("spring.cloud.config.discovery.enabled")
|
||||
@Import({ KubernetesClientAutoConfiguration.class, KubernetesReactiveDiscoveryClientAutoConfiguration.class })
|
||||
public class KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests {
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (this.context != null) {
|
||||
if (this.context.getParent() != null) {
|
||||
((AnnotationConfigApplicationContext) this.context.getParent()).close();
|
||||
}
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onWhenRequested() throws Exception {
|
||||
setup("server.port=7000", "spring.cloud.config.discovery.enabled=true",
|
||||
"spring.cloud.kubernetes.discovery.enabled:true", "spring.cloud.kubernetes.enabled:true",
|
||||
"spring.application.name:test", "spring.cloud.config.discovery.service-id:configserver");
|
||||
assertEquals(1, this.context.getParent().getBeanNamesForType(DiscoveryClient.class).length);
|
||||
|
||||
DiscoveryClient client = this.context.getParent().getBean(DiscoveryClient.class);
|
||||
verify(client, atLeast(2)).getInstances("configserver");
|
||||
ConfigClientProperties locator = this.context.getBean(ConfigClientProperties.class);
|
||||
assertEquals("http://fake:8888/", locator.getUri()[0]);
|
||||
}
|
||||
|
||||
private void setup(String... env) {
|
||||
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
|
||||
TestPropertyValues.of(env).applyTo(parent);
|
||||
parent.register(UtilAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
|
||||
EnvironmentKnobbler.class, KubernetesCommonsAutoConfiguration.class,
|
||||
KubernetesClientAutoConfiguration.class, KubernetesReactiveDiscoveryClientAutoConfiguration.class,
|
||||
DiscoveryClientConfigServiceBootstrapConfiguration.class, ConfigClientProperties.class);
|
||||
parent.refresh();
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.setParent(parent);
|
||||
this.context.register(PropertyPlaceholderAutoConfiguration.class, KubernetesCommonsAutoConfiguration.class,
|
||||
KubernetesReactiveDiscoveryClientAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
protected static class EnvironmentKnobbler {
|
||||
|
||||
@Bean
|
||||
public KubernetesInformerDiscoveryClient kubernetesInformerDiscoveryClient() {
|
||||
KubernetesInformerDiscoveryClient client = mock(KubernetesInformerDiscoveryClient.class);
|
||||
ServiceInstance instance = new DefaultServiceInstance("configserver1", "configserver", "fake", 8888, false);
|
||||
given(client.getInstances("configserver")).willReturn(Collections.singletonList(instance));
|
||||
return client;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.HashMap;
|
||||
|
||||
import io.kubernetes.client.informer.SharedInformerFactory;
|
||||
import io.kubernetes.client.informer.cache.Cache;
|
||||
import io.kubernetes.client.informer.cache.Lister;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddress;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointPort;
|
||||
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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class KubernetesInformerDiscoveryClientTests {
|
||||
|
||||
@Mock
|
||||
private SharedInformerFactory sharedInformerFactory;
|
||||
|
||||
@Mock
|
||||
private KubernetesDiscoveryProperties kubernetesDiscoveryProperties;
|
||||
|
||||
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());
|
||||
|
||||
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 V1Endpoints testEndpoints1 = new V1Endpoints()
|
||||
.metadata(new V1ObjectMeta().name("test-svc-1").namespace("namespace1"))
|
||||
.addSubsetsItem(new V1EndpointSubset().addPortsItem(new V1EndpointPort().port(8080))
|
||||
.addAddressesItem(new V1EndpointAddress().ip("2.2.2.2")));
|
||||
|
||||
@Test
|
||||
public void testDiscoveryGetServicesAllNamespaceShouldWork() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(testService1, testService2);
|
||||
|
||||
when(kubernetesDiscoveryProperties.isAllNamespaces()).thenReturn(true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
|
||||
sharedInformerFactory, serviceLister, null, null, null, kubernetesDiscoveryProperties);
|
||||
|
||||
assertThat(discoveryClient.getServices().toArray())
|
||||
.containsOnly(testService1.getMetadata().getName(), testService2.getMetadata().getName());
|
||||
|
||||
verify(kubernetesDiscoveryProperties, times(1)).isAllNamespaces();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiscoveryGetServicesOneNamespaceShouldWork() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(testService1, testService2);
|
||||
|
||||
when(kubernetesDiscoveryProperties.isAllNamespaces()).thenReturn(false);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
|
||||
sharedInformerFactory, serviceLister, null, null, null, kubernetesDiscoveryProperties);
|
||||
|
||||
assertThat(discoveryClient.getServices().toArray())
|
||||
.containsOnly(testService1.getMetadata().getName());
|
||||
|
||||
verify(kubernetesDiscoveryProperties, times(1)).isAllNamespaces();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiscoveryGetInstanceAllNamespaceShouldWork() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(testService1, testService2);
|
||||
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
|
||||
|
||||
when(kubernetesDiscoveryProperties.isAllNamespaces()).thenReturn(true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("",
|
||||
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1")).containsOnly(
|
||||
new KubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080, new HashMap<>(), false));
|
||||
|
||||
verify(kubernetesDiscoveryProperties, times(1)).isAllNamespaces();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiscoveryGetInstanceOneNamespaceShouldWork() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(testService1, testService2);
|
||||
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(testEndpoints1);
|
||||
|
||||
when(kubernetesDiscoveryProperties.isAllNamespaces()).thenReturn(false);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1",
|
||||
sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1")).containsOnly(
|
||||
new KubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080, new HashMap<>(), false));
|
||||
verify(kubernetesDiscoveryProperties, times(1)).isAllNamespaces();
|
||||
}
|
||||
|
||||
private Lister<V1Service> setupServiceLister(V1Service... services) {
|
||||
Cache<V1Service> serviceCache = new Cache<>();
|
||||
Lister<V1Service> serviceLister = new Lister<>(serviceCache);
|
||||
for (V1Service svc : services) {
|
||||
serviceCache.add(svc);
|
||||
}
|
||||
return serviceLister;
|
||||
}
|
||||
|
||||
private Lister<V1Endpoints> setupEndpointsLister(V1Endpoints... endpoints) {
|
||||
Cache<V1Endpoints> endpointsCache = new Cache<>();
|
||||
Lister<V1Endpoints> endpointsLister = new Lister<>(endpointsCache);
|
||||
for (V1Endpoints ep : endpoints) {
|
||||
endpointsCache.add(ep);
|
||||
}
|
||||
return endpointsLister;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
|
||||
"spring.cloud.kubernetes.discovery.cacheLoadingTimeoutSeconds=5",
|
||||
"spring.cloud.kubernetes.discovery.waitCacheReady=false"
|
||||
})
|
||||
public class KubernetesReactiveDiscoveryClientAutoConfigurationTests {
|
||||
|
||||
@Autowired(required = false)
|
||||
private DiscoveryClient discoveryClient;
|
||||
|
||||
@Test
|
||||
public void kubernetesDiscoveryClientCreated() {
|
||||
assertThat(this.discoveryClient).isNotNull().isInstanceOf(CompositeDiscoveryClient.class);
|
||||
|
||||
CompositeDiscoveryClient composite = (CompositeDiscoveryClient) this.discoveryClient;
|
||||
assertThat(composite.getDiscoveryClients().stream()
|
||||
.anyMatch(dc -> dc instanceof KubernetesInformerDiscoveryClient)).isTrue();
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
protected static class TestConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class KubernetesServiceInstanceTests {
|
||||
|
||||
@Test
|
||||
public void schemeIsHttp() {
|
||||
assertServiceInstance(false);
|
||||
}
|
||||
|
||||
private KubernetesServiceInstance assertServiceInstance(boolean secure) {
|
||||
KubernetesServiceInstance instance = new KubernetesServiceInstance("123", "myservice", "1.2.3.4", 8080,
|
||||
Collections.emptyMap(), secure);
|
||||
|
||||
assertThat(instance.getInstanceId()).isEqualTo("123");
|
||||
assertThat(instance.getServiceId()).isEqualTo("myservice");
|
||||
assertThat(instance.getHost()).isEqualTo("1.2.3.4");
|
||||
assertThat(instance.getPort()).isEqualTo(8080);
|
||||
assertThat(instance.isSecure()).isEqualTo(secure);
|
||||
assertThat(instance.getScheme()).isEqualTo(secure ? "https" : "http");
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void schemeIsHttps() {
|
||||
assertServiceInstance(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.gson;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1ManagedFieldsEntry;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class EndpointsTrimmingStrategyTests {
|
||||
|
||||
@Test
|
||||
public void testDeserializingEndpoints() {
|
||||
Gson gson = new Gson().newBuilder().addDeserializationExclusionStrategy(new EndpointsTrimmingStrategy())
|
||||
.create();
|
||||
V1Endpoints input = new V1Endpoints()
|
||||
.metadata(new V1ObjectMeta().name("foo").managedFields(Arrays.asList(new V1ManagedFieldsEntry())));
|
||||
|
||||
String data = gson.toJson(input);
|
||||
V1Endpoints output = gson.fromJson(data, V1Endpoints.class);
|
||||
|
||||
// managed-fields should be excluded
|
||||
Assert.assertNull(output.getMetadata().getManagedFields());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.gson;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.kubernetes.client.openapi.models.V1LoadBalancerIngress;
|
||||
import io.kubernetes.client.openapi.models.V1LoadBalancerStatus;
|
||||
import io.kubernetes.client.openapi.models.V1ManagedFieldsEntry;
|
||||
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.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ServiceTrimmingStrategyTests {
|
||||
|
||||
@Test
|
||||
public void testDeserializingService() {
|
||||
Gson gson = new Gson().newBuilder().addDeserializationExclusionStrategy(new ServiceTrimmingStrategy()).create();
|
||||
V1Service input = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("foo").managedFields(Arrays.asList(new V1ManagedFieldsEntry())))
|
||||
.spec(new V1ServiceSpec().loadBalancerIP("1.1.1.1")).status(new V1ServiceStatus().loadBalancer(
|
||||
new V1LoadBalancerStatus().addIngressItem(new V1LoadBalancerIngress().ip("2.2.2.2"))));
|
||||
String data = gson.toJson(input);
|
||||
V1Service output = gson.fromJson(data, V1Service.class);
|
||||
|
||||
// spec should be excluded
|
||||
Assert.assertNull(output.getSpec());
|
||||
// status should be excluded
|
||||
Assert.assertNull(output.getStatus());
|
||||
// managed-fields should be excluded
|
||||
Assert.assertNull(output.getMetadata().getManagedFields());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -61,6 +61,11 @@
|
||||
<artifactId>client-java-extended</artifactId>
|
||||
<version>${kubernetes-java-client.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java-spring-integration</artifactId>
|
||||
<version>${kubernetes-java-client.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>me.snowdrop</groupId>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>discovery-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>kubernetes-client-discovery</artifactId>
|
||||
<name>Spring Cloud Kubernetes :: Integration Tests :: Kubernetes Client Discovery</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-client-discovery</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,10 @@
|
||||
# We need this fragment in order for the kubernetes client to talk to
|
||||
# the Kubernetes API without caring about proper certificates
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: KUBERNETES_TRUST_CERTIFICATES
|
||||
value: true
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# we are using an FMP fragment to ensure that NodePort is used correctly
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: ${project.artifactId}
|
||||
labels:
|
||||
app: ${project.artifactId}
|
||||
spec:
|
||||
selector:
|
||||
app: ${project.artifactId}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
nodePort: ${nodeport.value}
|
||||
type: NodePort
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.it;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@SpringBootApplication
|
||||
@RestController
|
||||
public class DiscoveryClientApplication {
|
||||
|
||||
@Autowired
|
||||
private DiscoveryClient discoveryClient;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DiscoveryClientApplication.class, args);
|
||||
}
|
||||
|
||||
@GetMapping("/services")
|
||||
public List<String> services() {
|
||||
return this.discoveryClient.getServices();
|
||||
}
|
||||
|
||||
@GetMapping("/services/{service}/instances")
|
||||
public List<ServiceInstance> instances(@PathVariable("service") String service) {
|
||||
return this.discoveryClient.getInstances(service);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
<module>discovery-service-a</module>
|
||||
<module>discovery-service-b</module>
|
||||
<module>discovery-client</module>
|
||||
<module>kubernetes-client-discovery</module>
|
||||
<module>tests</module>
|
||||
</modules>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user