Complete alignment between fabric8 and k8s discovery clients (#1500)
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.discovery;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddress;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectReference;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.InstanceIdHostPodName;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
final class K8sInstanceIdHostPodNameSupplier implements Supplier<InstanceIdHostPodName> {
|
||||
|
||||
private final V1EndpointAddress endpointAddress;
|
||||
|
||||
private final V1Service service;
|
||||
|
||||
private K8sInstanceIdHostPodNameSupplier(V1EndpointAddress endpointAddress, V1Service service) {
|
||||
this.endpointAddress = endpointAddress;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstanceIdHostPodName get() {
|
||||
return new InstanceIdHostPodName(instanceId(), host(), podName());
|
||||
}
|
||||
|
||||
/**
|
||||
* to be used when .spec.type of the Service is != 'ExternalName'.
|
||||
*/
|
||||
static K8sInstanceIdHostPodNameSupplier nonExternalName(V1EndpointAddress endpointAddress, V1Service service) {
|
||||
return new K8sInstanceIdHostPodNameSupplier(endpointAddress, service);
|
||||
}
|
||||
|
||||
/**
|
||||
* to be used when .spec.type of the Service is == 'ExternalName'.
|
||||
*/
|
||||
static K8sInstanceIdHostPodNameSupplier externalName(V1Service service) {
|
||||
return new K8sInstanceIdHostPodNameSupplier(null, service);
|
||||
}
|
||||
|
||||
// instanceId is usually the pod-uid as seen in the .metadata.uid
|
||||
private String instanceId() {
|
||||
return Optional.ofNullable(endpointAddress).map(V1EndpointAddress::getTargetRef).map(V1ObjectReference::getUid)
|
||||
.orElseGet(() -> service.getMetadata().getUid());
|
||||
}
|
||||
|
||||
private String host() {
|
||||
return Optional.ofNullable(endpointAddress).map(V1EndpointAddress::getIp)
|
||||
.orElseGet(() -> service.getSpec().getExternalName());
|
||||
}
|
||||
|
||||
private String podName() {
|
||||
return Optional.ofNullable(endpointAddress).map(V1EndpointAddress::getTargetRef)
|
||||
.filter(objectReference -> "Pod".equals(objectReference.getKind())).map(V1ObjectReference::getName)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.discovery;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.PodLabelsAndAnnotations;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
final class K8sPodLabelsAndAnnotationsSupplier implements Function<String, PodLabelsAndAnnotations> {
|
||||
|
||||
private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(K8sPodLabelsAndAnnotationsSupplier.class));
|
||||
|
||||
private final CoreV1Api coreV1Api;
|
||||
|
||||
private final String namespace;
|
||||
|
||||
private K8sPodLabelsAndAnnotationsSupplier(CoreV1Api coreV1Api, String namespace) {
|
||||
this.coreV1Api = coreV1Api;
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* to be used when .spec.type of the Service is != 'ExternalName'.
|
||||
*/
|
||||
static K8sPodLabelsAndAnnotationsSupplier nonExternalName(CoreV1Api coreV1Api, String namespace) {
|
||||
return new K8sPodLabelsAndAnnotationsSupplier(coreV1Api, namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* to be used when .spec.type of the Service is == 'ExternalName'.
|
||||
*/
|
||||
static K8sPodLabelsAndAnnotationsSupplier externalName() {
|
||||
return new K8sPodLabelsAndAnnotationsSupplier(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PodLabelsAndAnnotations apply(String podName) {
|
||||
|
||||
V1ObjectMeta objectMeta;
|
||||
|
||||
try {
|
||||
objectMeta = Optional.ofNullable(coreV1Api.readNamespacedPod(podName, namespace, null).getMetadata())
|
||||
.orElse(new V1ObjectMetaBuilder().withLabels(Map.of()).withAnnotations(Map.of()).build());
|
||||
}
|
||||
catch (ApiException e) {
|
||||
LOG.warn(e, "Could not get pod metadata");
|
||||
objectMeta = new V1ObjectMetaBuilder().withLabels(Map.of()).withAnnotations(Map.of()).build();
|
||||
}
|
||||
|
||||
return new PodLabelsAndAnnotations(Optional.ofNullable(objectMeta.getLabels()).orElse(Map.of()),
|
||||
Optional.ofNullable(objectMeta.getAnnotations()).orElse(Map.of()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,15 +17,19 @@
|
||||
package org.springframework.cloud.kubernetes.client.discovery;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.kubernetes.client.informer.SharedInformerFactory;
|
||||
import io.kubernetes.client.informer.cache.Lister;
|
||||
import io.kubernetes.client.openapi.models.CoreV1EndpointPort;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddress;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointSubset;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceSpec;
|
||||
@@ -33,14 +37,15 @@ import io.kubernetes.client.util.wait.Wait;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.ServiceMetadata;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.keysWithPrefix;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.NAMESPACE_METADATA_KEY;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.SERVICE_TYPE;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.UNSET_PORT_NAME;
|
||||
import static org.springframework.util.StringUtils.hasText;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
@@ -82,40 +87,6 @@ final class KubernetesDiscoveryClientUtils {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds the following metadata. <pre>
|
||||
* - labels (if requested)
|
||||
* - annotations (if requested)
|
||||
* - metadata
|
||||
* - service type
|
||||
* </pre>
|
||||
*/
|
||||
static Map<String, String> serviceMetadata(KubernetesDiscoveryProperties properties, V1Service service,
|
||||
String serviceId) {
|
||||
|
||||
Map<String, String> serviceMetadata = new HashMap<>();
|
||||
KubernetesDiscoveryProperties.Metadata metadataProps = properties.metadata();
|
||||
if (metadataProps.addLabels()) {
|
||||
Map<String, String> labelMetadata = keysWithPrefix(service.getMetadata().getLabels(),
|
||||
metadataProps.labelsPrefix());
|
||||
LOG.debug(() -> "Adding labels metadata: " + labelMetadata + " for serviceId: " + serviceId);
|
||||
serviceMetadata.putAll(labelMetadata);
|
||||
}
|
||||
if (metadataProps.addAnnotations()) {
|
||||
Map<String, String> annotationMetadata = keysWithPrefix(service.getMetadata().getAnnotations(),
|
||||
metadataProps.annotationsPrefix());
|
||||
LOG.debug(() -> "Adding annotations metadata: " + annotationMetadata + " for serviceId: " + serviceId);
|
||||
serviceMetadata.putAll(annotationMetadata);
|
||||
}
|
||||
|
||||
serviceMetadata.put(NAMESPACE_METADATA_KEY,
|
||||
Optional.ofNullable(service.getMetadata()).map(V1ObjectMeta::getNamespace).orElse(null));
|
||||
serviceMetadata.put(SERVICE_TYPE,
|
||||
Optional.ofNullable(service.getSpec()).map(V1ServiceSpec::getType).orElse(null));
|
||||
|
||||
return serviceMetadata;
|
||||
}
|
||||
|
||||
static Predicate<V1Service> filter(KubernetesDiscoveryProperties properties) {
|
||||
String spelExpression = properties.filter();
|
||||
Predicate<V1Service> predicate;
|
||||
@@ -159,4 +130,38 @@ final class KubernetesDiscoveryClientUtils {
|
||||
|
||||
}
|
||||
|
||||
static ServiceMetadata serviceMetadata(V1Service service) {
|
||||
V1ObjectMeta metadata = service.getMetadata();
|
||||
V1ServiceSpec serviceSpec = service.getSpec();
|
||||
return new ServiceMetadata(metadata.getName(), metadata.getNamespace(), serviceSpec.getType(),
|
||||
metadata.getLabels(), metadata.getAnnotations());
|
||||
}
|
||||
|
||||
/**
|
||||
* a service is allowed to have a single port defined without a name.
|
||||
*/
|
||||
static Map<String, Integer> endpointSubsetsPortData(List<V1EndpointSubset> endpointSubsets) {
|
||||
return endpointSubsets.stream()
|
||||
.flatMap(endpointSubset -> Optional.ofNullable(endpointSubset.getPorts()).orElse(List.of()).stream())
|
||||
.collect(Collectors.toMap(
|
||||
endpointPort -> hasText(endpointPort.getName()) ? endpointPort.getName() : UNSET_PORT_NAME,
|
||||
CoreV1EndpointPort::getPort));
|
||||
}
|
||||
|
||||
static List<V1EndpointAddress> addresses(V1EndpointSubset endpointSubset,
|
||||
KubernetesDiscoveryProperties properties) {
|
||||
List<V1EndpointAddress> addresses = Optional.ofNullable(endpointSubset.getAddresses()).map(ArrayList::new)
|
||||
.orElse(new ArrayList<>());
|
||||
|
||||
if (properties.includeNotReadyAddresses()) {
|
||||
List<V1EndpointAddress> notReadyAddresses = endpointSubset.getNotReadyAddresses();
|
||||
if (CollectionUtils.isEmpty(notReadyAddresses)) {
|
||||
return addresses;
|
||||
}
|
||||
addresses.addAll(notReadyAddresses);
|
||||
}
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,43 +17,47 @@
|
||||
package org.springframework.cloud.kubernetes.client.discovery;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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.CoreV1EndpointPort;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddress;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointSubset;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.ServiceMetadata;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.ServicePortNameAndNumber;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.ServicePortSecureResolver;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.cloud.kubernetes.client.discovery.K8sInstanceIdHostPodNameSupplier.externalName;
|
||||
import static org.springframework.cloud.kubernetes.client.discovery.K8sInstanceIdHostPodNameSupplier.nonExternalName;
|
||||
import static org.springframework.cloud.kubernetes.client.discovery.K8sPodLabelsAndAnnotationsSupplier.externalName;
|
||||
import static org.springframework.cloud.kubernetes.client.discovery.K8sPodLabelsAndAnnotationsSupplier.nonExternalName;
|
||||
import static org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientUtils.addresses;
|
||||
import static org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientUtils.endpointSubsetsPortData;
|
||||
import static org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientUtils.filter;
|
||||
import static org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientUtils.matchesServiceLabels;
|
||||
import static org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientUtils.postConstruct;
|
||||
import static org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientUtils.serviceMetadata;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.HTTP;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.HTTPS;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.PRIMARY_PORT_NAME_LABEL_KEY;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.SECURED;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.UNSET_PORT_NAME;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.DiscoveryClientUtils.endpointsPort;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.DiscoveryClientUtils.serviceInstance;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.DiscoveryClientUtils.serviceInstanceMetadata;
|
||||
import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.EXTERNAL_NAME;
|
||||
|
||||
/**
|
||||
* @author Min Kim
|
||||
@@ -76,6 +80,13 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
|
||||
|
||||
private final Predicate<V1Service> filter;
|
||||
|
||||
private final ServicePortSecureResolver servicePortSecureResolver;
|
||||
|
||||
// visible only for testing and
|
||||
// must be constructor injected in a future release
|
||||
@Autowired
|
||||
CoreV1Api coreV1Api;
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public KubernetesInformerDiscoveryClient(String namespace, SharedInformerFactory sharedInformerFactory,
|
||||
Lister<V1Service> serviceLister, Lister<V1Endpoints> endpointsLister,
|
||||
@@ -87,6 +98,7 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
|
||||
this.informersReadyFunc = () -> serviceInformer.hasSynced() && endpointsInformer.hasSynced();
|
||||
this.properties = properties;
|
||||
filter = filter(properties);
|
||||
servicePortSecureResolver = new ServicePortSecureResolver(properties);
|
||||
}
|
||||
|
||||
public KubernetesInformerDiscoveryClient(SharedInformerFactory sharedInformerFactory,
|
||||
@@ -99,6 +111,7 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
|
||||
this.informersReadyFunc = () -> serviceInformer.hasSynced() && endpointsInformer.hasSynced();
|
||||
this.properties = properties;
|
||||
filter = filter(properties);
|
||||
servicePortSecureResolver = new ServicePortSecureResolver(properties);
|
||||
}
|
||||
|
||||
public KubernetesInformerDiscoveryClient(List<SharedInformerFactory> sharedInformerFactories,
|
||||
@@ -119,6 +132,7 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
|
||||
|
||||
this.properties = properties;
|
||||
filter = filter(properties);
|
||||
servicePortSecureResolver = new ServicePortSecureResolver(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,104 +144,76 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient {
|
||||
public List<ServiceInstance> getInstances(String serviceId) {
|
||||
Objects.requireNonNull(serviceId, "serviceId must be provided");
|
||||
|
||||
List<V1Service> services = serviceListers.stream().flatMap(x -> x.list().stream())
|
||||
List<V1Service> allServices = serviceListers.stream().flatMap(x -> x.list().stream())
|
||||
.filter(scv -> scv.getMetadata() != null).filter(svc -> serviceId.equals(svc.getMetadata().getName()))
|
||||
.filter(scv -> matchesServiceLabels(scv, properties)).filter(filter).toList();
|
||||
return services.stream().flatMap(service -> getServiceInstanceDetails(service, serviceId)).toList();
|
||||
}
|
||||
.filter(scv -> matchesServiceLabels(scv, properties)).toList();
|
||||
|
||||
private Stream<ServiceInstance> getServiceInstanceDetails(V1Service service, String serviceId) {
|
||||
Map<String, String> serviceMetadata = serviceMetadata(properties, service, serviceId);
|
||||
List<ServiceInstance> serviceInstances = allServices.stream().filter(filter)
|
||||
.flatMap(service -> serviceInstances(service, serviceId).stream())
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
|
||||
List<V1Endpoints> endpoints = endpointsListers.stream()
|
||||
.map(endpointsLister -> endpointsLister.namespace(service.getMetadata().getNamespace())
|
||||
.get(service.getMetadata().getName()))
|
||||
.filter(Objects::nonNull).filter(ep -> ep.getSubsets() != null).toList();
|
||||
if (properties.includeExternalNameServices()) {
|
||||
LOG.debug(() -> "Searching for 'ExternalName' type of services with serviceId : " + serviceId);
|
||||
List<V1Service> externalNameServices = allServices.stream().filter(s -> s.getSpec() != null)
|
||||
.filter(s -> EXTERNAL_NAME.equals(s.getSpec().getType())).toList();
|
||||
for (V1Service service : externalNameServices) {
|
||||
ServiceMetadata serviceMetadata = serviceMetadata(service);
|
||||
Map<String, String> serviceInstanceMetadata = serviceInstanceMetadata(Map.of(), serviceMetadata,
|
||||
properties);
|
||||
|
||||
Optional<String> discoveredPrimaryPortName = Optional.empty();
|
||||
if (service.getMetadata() != null && service.getMetadata().getLabels() != null) {
|
||||
discoveredPrimaryPortName = Optional
|
||||
.ofNullable(service.getMetadata().getLabels().get(PRIMARY_PORT_NAME_LABEL_KEY));
|
||||
}
|
||||
final String primaryPortName = discoveredPrimaryPortName.orElse(properties.primaryPortName());
|
||||
K8sInstanceIdHostPodNameSupplier supplierOne = externalName(service);
|
||||
K8sPodLabelsAndAnnotationsSupplier supplierTwo = externalName();
|
||||
|
||||
final boolean secured = isSecured(service);
|
||||
|
||||
return endpoints.stream()
|
||||
.flatMap(ep -> ep.getSubsets().stream()
|
||||
.filter(subset -> subset.getPorts() != null && subset.getPorts().size() > 0) // safeguard
|
||||
.flatMap(subset -> {
|
||||
Map<String, String> metadata = new HashMap<>(serviceMetadata);
|
||||
List<CoreV1EndpointPort> endpointPorts = subset.getPorts();
|
||||
if (properties.metadata() != null && properties.metadata().addPorts()) {
|
||||
endpointPorts.forEach(p -> metadata.put(
|
||||
StringUtils.hasText(p.getName()) ? p.getName() : UNSET_PORT_NAME,
|
||||
Integer.toString(p.getPort())));
|
||||
}
|
||||
List<V1EndpointAddress> addresses = subset.getAddresses();
|
||||
if (addresses == null) {
|
||||
addresses = new ArrayList<>();
|
||||
}
|
||||
if (properties.includeNotReadyAddresses()
|
||||
&& !CollectionUtils.isEmpty(subset.getNotReadyAddresses())) {
|
||||
addresses.addAll(subset.getNotReadyAddresses());
|
||||
}
|
||||
|
||||
final int port = findEndpointPort(endpointPorts, primaryPortName, serviceId);
|
||||
return addresses.stream()
|
||||
.map(addr -> new DefaultKubernetesServiceInstance(
|
||||
addr.getTargetRef() != null ? addr.getTargetRef().getUid() : "", serviceId,
|
||||
addr.getIp(), port, metadata, secured, service.getMetadata().getNamespace(),
|
||||
// TODO find out how to get cluster name
|
||||
// possibly from
|
||||
// KubeConfig
|
||||
null));
|
||||
}));
|
||||
}
|
||||
|
||||
private static boolean isSecured(V1Service service) {
|
||||
Optional<String> securedOpt = Optional.empty();
|
||||
if (service.getMetadata() != null && service.getMetadata().getAnnotations() != null) {
|
||||
securedOpt = Optional.ofNullable(service.getMetadata().getAnnotations().get(SECURED));
|
||||
}
|
||||
if (!securedOpt.isPresent() && service.getMetadata() != null && service.getMetadata().getLabels() != null) {
|
||||
securedOpt = Optional.ofNullable(service.getMetadata().getLabels().get(SECURED));
|
||||
}
|
||||
return Boolean.parseBoolean(securedOpt.orElse("false"));
|
||||
}
|
||||
|
||||
private int findEndpointPort(List<CoreV1EndpointPort> endpointPorts, String primaryPortName, String serviceId) {
|
||||
if (endpointPorts.size() == 1) {
|
||||
return endpointPorts.get(0).getPort();
|
||||
}
|
||||
else {
|
||||
Map<String, Integer> ports = endpointPorts.stream().filter(p -> StringUtils.hasText(p.getName()))
|
||||
.collect(Collectors.toMap(CoreV1EndpointPort::getName, CoreV1EndpointPort::getPort));
|
||||
// This oneliner is looking for a port with a name equal to the primary port
|
||||
// name specified in the service label
|
||||
// or in spring.cloud.kubernetes.discovery.primary-port-name, equal to https,
|
||||
// or equal to http.
|
||||
// In case no port has been found return -1 to log a warning and fall back to
|
||||
// the first port in the list.
|
||||
int discoveredPort = ports.getOrDefault(primaryPortName,
|
||||
ports.getOrDefault(HTTPS, ports.getOrDefault(HTTP, -1)));
|
||||
|
||||
if (discoveredPort == -1) {
|
||||
if (StringUtils.hasText(primaryPortName)) {
|
||||
LOG.warn(() -> "Could not find a port named '" + primaryPortName
|
||||
+ "', 'https', or 'http' for service '" + serviceId + "'.");
|
||||
}
|
||||
else {
|
||||
LOG.warn(() -> "Could not find a port named 'https' or 'http' for service '" + serviceId + "'.");
|
||||
}
|
||||
LOG.warn(
|
||||
() -> "Make sure that either the primary-port-name label has been added to the service, or that spring.cloud.kubernetes.discovery.primary-port-name has been configured.");
|
||||
LOG.warn(() -> "Alternatively name the primary port 'https' or 'http'");
|
||||
LOG.warn(() -> "An incorrect configuration may result in non-deterministic behaviour.");
|
||||
discoveredPort = endpointPorts.get(0).getPort();
|
||||
ServiceInstance externalNameServiceInstance = serviceInstance(null, serviceMetadata, supplierOne,
|
||||
supplierTwo, new ServicePortNameAndNumber(-1, null), serviceInstanceMetadata, properties);
|
||||
serviceInstances.add(externalNameServiceInstance);
|
||||
}
|
||||
return discoveredPort;
|
||||
}
|
||||
|
||||
return serviceInstances;
|
||||
}
|
||||
|
||||
private List<ServiceInstance> serviceInstances(V1Service service, String serviceId) {
|
||||
|
||||
List<ServiceInstance> instances = new ArrayList<>();
|
||||
|
||||
List<V1Endpoints> allEndpoints = endpointsListers.stream()
|
||||
.map(endpointsLister -> endpointsLister.namespace(service.getMetadata().getNamespace()).get(serviceId))
|
||||
.filter(Objects::nonNull).toList();
|
||||
|
||||
for (V1Endpoints endpoints : allEndpoints) {
|
||||
List<V1EndpointSubset> subsets = endpoints.getSubsets();
|
||||
if (subsets == null || subsets.isEmpty()) {
|
||||
LOG.debug(() -> "serviceId : " + serviceId + " does not have any subsets");
|
||||
}
|
||||
else {
|
||||
ServiceMetadata serviceMetadata = serviceMetadata(service);
|
||||
Map<String, Integer> portsData = endpointSubsetsPortData(subsets);
|
||||
Map<String, String> serviceInstanceMetadata = serviceInstanceMetadata(portsData, serviceMetadata,
|
||||
properties);
|
||||
|
||||
for (V1EndpointSubset endpointSubset : subsets) {
|
||||
|
||||
Map<String, Integer> endpointsPortData = endpointSubsetsPortData(List.of(endpointSubset));
|
||||
ServicePortNameAndNumber portData = endpointsPort(endpointsPortData, serviceMetadata, properties);
|
||||
|
||||
List<V1EndpointAddress> addresses = addresses(endpointSubset, properties);
|
||||
for (V1EndpointAddress endpointAddress : addresses) {
|
||||
|
||||
K8sInstanceIdHostPodNameSupplier supplierOne = nonExternalName(endpointAddress, service);
|
||||
K8sPodLabelsAndAnnotationsSupplier supplierTwo = nonExternalName(coreV1Api,
|
||||
service.getMetadata().getNamespace());
|
||||
|
||||
ServiceInstance serviceInstance = serviceInstance(servicePortSecureResolver, serviceMetadata,
|
||||
supplierOne, supplierTwo, portData, serviceInstanceMetadata, properties);
|
||||
instances.add(serviceInstance);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.openapi.models.V1EndpointAddress;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddressBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectReferenceBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceSpecBuilder;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.InstanceIdHostPodName;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class K8sInstanceIdHostPodNameSupplierTests {
|
||||
|
||||
@Test
|
||||
void instanceIdNoEndpointAddress() {
|
||||
V1Service service = new V1ServiceBuilder().withSpec(new V1ServiceSpecBuilder().build())
|
||||
.withMetadata(new V1ObjectMetaBuilder().withUid("123").build()).build();
|
||||
|
||||
K8sInstanceIdHostPodNameSupplier supplier = K8sInstanceIdHostPodNameSupplier.externalName(service);
|
||||
InstanceIdHostPodName result = supplier.get();
|
||||
|
||||
Assertions.assertNotNull(result);
|
||||
Assertions.assertEquals(result.instanceId(), "123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceIdWithEndpointAddress() {
|
||||
V1EndpointAddress endpointAddress = new V1EndpointAddressBuilder()
|
||||
.withTargetRef(new V1ObjectReferenceBuilder().withUid("456").build()).build();
|
||||
V1Service service = new V1ServiceBuilder().withSpec(new V1ServiceSpecBuilder().build())
|
||||
.withMetadata(new V1ObjectMetaBuilder().withUid("123").build()).build();
|
||||
|
||||
K8sInstanceIdHostPodNameSupplier supplier = K8sInstanceIdHostPodNameSupplier.nonExternalName(endpointAddress,
|
||||
service);
|
||||
InstanceIdHostPodName result = supplier.get();
|
||||
|
||||
Assertions.assertNotNull(result);
|
||||
Assertions.assertEquals(result.instanceId(), "456");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hostNoEndpointAddress() {
|
||||
V1Service service = new V1ServiceBuilder()
|
||||
.withSpec(new V1ServiceSpecBuilder().withExternalName("external-name").build())
|
||||
.withMetadata(new V1ObjectMeta()).build();
|
||||
|
||||
K8sInstanceIdHostPodNameSupplier supplier = K8sInstanceIdHostPodNameSupplier.externalName(service);
|
||||
InstanceIdHostPodName result = supplier.get();
|
||||
|
||||
Assertions.assertNotNull(result);
|
||||
Assertions.assertEquals(result.host(), "external-name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hostWithEndpointAddress() {
|
||||
V1EndpointAddress endpointAddress = new V1EndpointAddressBuilder().withIp("127.0.0.1").build();
|
||||
V1Service service = new V1ServiceBuilder()
|
||||
.withSpec(new V1ServiceSpecBuilder().withExternalName("external-name").build())
|
||||
.withMetadata(new V1ObjectMeta()).build();
|
||||
|
||||
K8sInstanceIdHostPodNameSupplier supplier = K8sInstanceIdHostPodNameSupplier.nonExternalName(endpointAddress,
|
||||
service);
|
||||
InstanceIdHostPodName result = supplier.get();
|
||||
|
||||
Assertions.assertNotNull(result);
|
||||
Assertions.assertEquals(result.host(), "127.0.0.1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPodNameIsNull() {
|
||||
V1Service service = new V1ServiceBuilder().withMetadata(new V1ObjectMetaBuilder().withUid("123").build())
|
||||
.withSpec(new V1ServiceSpecBuilder().withExternalName("external-name").build()).build();
|
||||
K8sInstanceIdHostPodNameSupplier supplier = K8sInstanceIdHostPodNameSupplier.externalName(service);
|
||||
InstanceIdHostPodName result = supplier.get();
|
||||
|
||||
Assertions.assertNotNull(result);
|
||||
Assertions.assertNull(result.podName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void podNameKindNotPod() {
|
||||
V1EndpointAddress endpointAddress = new V1EndpointAddressBuilder()
|
||||
.withTargetRef(new V1ObjectReferenceBuilder().withKind("Service").build()).build();
|
||||
V1Service service = new V1ServiceBuilder()
|
||||
.withSpec(new V1ServiceSpecBuilder().withExternalName("external-name").build())
|
||||
.withMetadata(new V1ObjectMeta()).build();
|
||||
|
||||
K8sInstanceIdHostPodNameSupplier supplier = K8sInstanceIdHostPodNameSupplier.nonExternalName(endpointAddress,
|
||||
service);
|
||||
InstanceIdHostPodName result = supplier.get();
|
||||
|
||||
Assertions.assertNotNull(result);
|
||||
Assertions.assertNull(result.podName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void podNameKindIsPod() {
|
||||
V1EndpointAddress endpointAddress = new V1EndpointAddressBuilder()
|
||||
.withTargetRef(new V1ObjectReferenceBuilder().withKind("Pod").withName("my-pod").build()).build();
|
||||
V1Service service = new V1ServiceBuilder()
|
||||
.withSpec(new V1ServiceSpecBuilder().withExternalName("external-name").build())
|
||||
.withMetadata(new V1ObjectMeta()).build();
|
||||
|
||||
K8sInstanceIdHostPodNameSupplier supplier = K8sInstanceIdHostPodNameSupplier.nonExternalName(endpointAddress,
|
||||
service);
|
||||
InstanceIdHostPodName result = supplier.get();
|
||||
|
||||
Assertions.assertNotNull(result);
|
||||
Assertions.assertEquals(result.podName(), "my-pod");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.discovery;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1PodBuilder;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.PodLabelsAndAnnotations;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class K8sPodLabelsAndAnnotationsSupplierTests {
|
||||
|
||||
private static final String NAMESPACE = "spring-k8s";
|
||||
|
||||
private static final String POD_NAME = "my-pod";
|
||||
|
||||
private final CoreV1Api coreV1Api = Mockito.mock(CoreV1Api.class);
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
Mockito.reset(coreV1Api);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noObjetMeta() throws Exception {
|
||||
|
||||
Mockito.when(coreV1Api.readNamespacedPod(POD_NAME, NAMESPACE, null)).thenReturn(
|
||||
new V1PodBuilder().withMetadata(new V1ObjectMetaBuilder().withName(POD_NAME).build()).build());
|
||||
|
||||
PodLabelsAndAnnotations result = K8sPodLabelsAndAnnotationsSupplier.nonExternalName(coreV1Api, NAMESPACE)
|
||||
.apply(POD_NAME);
|
||||
Assertions.assertNotNull(result);
|
||||
Assertions.assertTrue(result.labels().isEmpty());
|
||||
Assertions.assertTrue(result.annotations().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void labelsAndAnnotationsPresent() throws Exception {
|
||||
|
||||
Mockito.when(coreV1Api.readNamespacedPod(POD_NAME, NAMESPACE, null))
|
||||
.thenReturn(new V1PodBuilder().withMetadata(new V1ObjectMetaBuilder().withName(POD_NAME)
|
||||
.withLabels(Map.of("a", "b")).withAnnotations(Map.of("c", "d")).build()).build());
|
||||
|
||||
PodLabelsAndAnnotations result = K8sPodLabelsAndAnnotationsSupplier.nonExternalName(coreV1Api, NAMESPACE)
|
||||
.apply(POD_NAME);
|
||||
Assertions.assertNotNull(result);
|
||||
Assertions.assertEquals(result.labels(), Map.of("a", "b"));
|
||||
Assertions.assertEquals(result.annotations(), Map.of("c", "d"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import java.util.Collections;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.JSON;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import okhttp3.OkHttpClient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
@@ -91,6 +92,11 @@ public class KubernetesDiscoveryClientConfigClientBootstrapConfigurationTests {
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
protected static class EnvironmentKnobbler {
|
||||
|
||||
@Bean
|
||||
CoreV1Api coreV1Api(ApiClient apiClient) {
|
||||
return new CoreV1Api(apiClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ApiClient apiClient() {
|
||||
ApiClient apiClient = mock(ApiClient.class);
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.discovery;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import io.kubernetes.client.informer.cache.Cache;
|
||||
import io.kubernetes.client.informer.cache.Lister;
|
||||
import io.kubernetes.client.openapi.models.CoreV1EndpointPort;
|
||||
import io.kubernetes.client.openapi.models.CoreV1EndpointPortBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ServicePort;
|
||||
import io.kubernetes.client.openapi.models.V1ServicePortBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceSpecBuilder;
|
||||
import org.assertj.core.util.Strings;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
|
||||
private static final SharedInformerFactoryStub STUB = new SharedInformerFactoryStub();
|
||||
|
||||
private static final SharedInformerStub<V1Service> SERVICE_SHARED_INFORMER_STUB = new SharedInformerStub<>();
|
||||
|
||||
private static final SharedInformerStub<V1Endpoints> ENDPOINTS_SHARED_INFORMER_STUB = new SharedInformerStub<>();
|
||||
|
||||
private Cache<V1Service> servicesCache;
|
||||
|
||||
private Lister<V1Service> servicesLister;
|
||||
|
||||
private Cache<V1Endpoints> endpointsCache;
|
||||
|
||||
private Lister<V1Endpoints> endpointsLister;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
servicesCache = new Cache<>();
|
||||
servicesLister = new Lister<>(servicesCache);
|
||||
|
||||
endpointsCache = new Cache<>();
|
||||
endpointsLister = new Lister<>(endpointsCache);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAllExtraMetadataDisabled() {
|
||||
String serviceId = "s";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, null, false,
|
||||
null, false, null);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
|
||||
false, null, Set.of(), Map.of(), null, metadata, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
setup(serviceId, "ns", Map.of("l1", "lab"), Map.of("l1", "lab"), Map.of(80, "http", 5555, ""));
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).isEqualTo(Map.of("k8s_namespace", "ns", "type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLabelsEnabled() {
|
||||
String serviceId = "s";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(true, null, false,
|
||||
null, false, null);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
|
||||
false, null, Set.of(), Map.of(), null, metadata, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
setup(serviceId, "ns", Map.of("l1", "v1", "l2", "v2"), Map.of("l1", "lab"), Map.of(80, "http", 5555, ""));
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("l1", "v1"), entry("l2", "v2"),
|
||||
entry("k8s_namespace", "ns"), entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLabelsEnabledWithPrefix() {
|
||||
String serviceId = "s";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(true, "l_", false,
|
||||
null, false, null);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
|
||||
false, null, Set.of(), Map.of(), null, metadata, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
setup(serviceId, "ns", Map.of("l1", "v1", "l2", "v2"), Map.of("l1", "lab"), Map.of(80, "http", 5555, ""));
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("l_l1", "v1"), entry("l_l2", "v2"),
|
||||
entry("k8s_namespace", "ns"), entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAnnotationsEnabled() {
|
||||
String serviceId = "s";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, null, true,
|
||||
null, false, null);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
|
||||
false, null, Set.of(), Map.of(), null, metadata, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
setup(serviceId, "ns", Map.of("l1", "v1"), Map.of("a1", "v1", "a2", "v2"), Map.of(80, "http", 5555, ""));
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("a1", "v1"), entry("a2", "v2"),
|
||||
entry("k8s_namespace", "ns"), entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAnnotationsEnabledWithPrefix() {
|
||||
String serviceId = "s";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, null, true,
|
||||
"a_", false, null);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
|
||||
false, null, Set.of(), Map.of(), null, metadata, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
setup(serviceId, "ns", Map.of("l1", "v1"), Map.of("a1", "v1", "a2", "v2"), Map.of(80, "http", 5555, ""));
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("a_a1", "v1"), entry("a_a2", "v2"),
|
||||
entry("k8s_namespace", "ns"), entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPortsEnabled() {
|
||||
String serviceId = "s";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, null, false,
|
||||
null, true, null);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
|
||||
false, null, Set.of(), Map.of(), null, metadata, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
setup(serviceId, "test", Map.of("l1", "v1"), Map.of("a1", "v1", "a2", "v2"), Map.of(80, "http", 5555, ""));
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("http", "80"), entry("k8s_namespace", "test"),
|
||||
entry("<unset>", "5555"), entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPortsEnabledWithPrefix() {
|
||||
String serviceId = "s";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, null, false,
|
||||
null, true, "p_");
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
|
||||
false, null, Set.of(), Map.of(), null, metadata, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
setup(serviceId, "ns", Map.of("l1", "v1"), Map.of("a1", "v1", "a2", "v2"), Map.of(80, "http", 5555, ""));
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("p_http", "80"), entry("k8s_namespace", "ns"),
|
||||
entry("p_<unset>", "5555"), entry("type", "ClusterIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLabelsAndAnnotationsAndPortsEnabledWithPrefix() {
|
||||
String serviceId = "s";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(true, "l_", true,
|
||||
"a_", true, "p_");
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
|
||||
false, null, Set.of(), Map.of(), null, metadata, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
setup(serviceId, "ns", Map.of("l1", "la1"), Map.of("a1", "an1", "a2", "an2"), Map.of(80, "http", 5555, ""));
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).getMetadata()).containsOnly(entry("a_a1", "an1"), entry("a_a2", "an2"),
|
||||
entry("l_l1", "la1"), entry("p_http", "80"), entry("k8s_namespace", "ns"), entry("type", "ClusterIP"),
|
||||
entry("p_<unset>", "5555"));
|
||||
}
|
||||
|
||||
private void setup(String serviceId, String namespace, Map<String, String> labels, Map<String, String> annotations,
|
||||
Map<Integer, String> ports) {
|
||||
|
||||
V1Service service = new V1ServiceBuilder()
|
||||
.withSpec(new V1ServiceSpecBuilder().withType("ClusterIP").withPorts(getServicePorts(ports)).build())
|
||||
.withNewMetadata().withName(serviceId).withNamespace(namespace).withLabels(labels)
|
||||
.withAnnotations(annotations).endMetadata().build();
|
||||
|
||||
servicesCache.add(service);
|
||||
|
||||
V1ObjectMeta objectMeta = new V1ObjectMeta();
|
||||
objectMeta.setNamespace(namespace);
|
||||
objectMeta.setName(serviceId);
|
||||
|
||||
V1Endpoints endpoints = new V1EndpointsBuilder().withMetadata(objectMeta).addNewSubset()
|
||||
.addAllToPorts(getEndpointPorts(ports)).addNewAddress().endAddress().endSubset().build();
|
||||
|
||||
endpointsCache.add(endpoints);
|
||||
|
||||
}
|
||||
|
||||
private List<V1ServicePort> getServicePorts(Map<Integer, String> ports) {
|
||||
return ports.entrySet().stream().map(e -> {
|
||||
V1ServicePortBuilder servicePortBuilder = new V1ServicePortBuilder();
|
||||
servicePortBuilder.withPort(e.getKey());
|
||||
if (!Strings.isNullOrEmpty(e.getValue())) {
|
||||
servicePortBuilder.withName(e.getValue());
|
||||
}
|
||||
return servicePortBuilder.build();
|
||||
}).collect(toList());
|
||||
}
|
||||
|
||||
private List<CoreV1EndpointPort> getEndpointPorts(Map<Integer, String> ports) {
|
||||
return ports.entrySet().stream().map(e -> {
|
||||
CoreV1EndpointPortBuilder endpointPortBuilder = new CoreV1EndpointPortBuilder();
|
||||
endpointPortBuilder.withPort(e.getKey());
|
||||
if (!Strings.isNullOrEmpty(e.getValue())) {
|
||||
endpointPortBuilder.withName(e.getValue());
|
||||
}
|
||||
return endpointPortBuilder.build();
|
||||
}).collect(toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.discovery;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceSpec;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class KubernetesDiscoveryClientServiceMetadataTests {
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - labels are not added
|
||||
* - annotations are not added
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServiceMetadataEmpty() {
|
||||
boolean addLabels = false;
|
||||
String labelsPrefix = "";
|
||||
boolean addAnnotations = false;
|
||||
String annotationsPrefix = "";
|
||||
boolean addPorts = false;
|
||||
String portsPrefix = "";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
V1Service service = new V1Service().spec(new V1ServiceSpec().type("ClusterIP"))
|
||||
.metadata(new V1ObjectMeta().namespace("default"));
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata(properties, service, "my-service");
|
||||
Assertions.assertEquals(result.size(), 2);
|
||||
Assertions.assertEquals(result, Map.of("k8s_namespace", "default", "type", "ClusterIP"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - labels are added without a prefix
|
||||
* - annotations are not added
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServiceMetadataAddLabelsNoPrefix(CapturedOutput output) {
|
||||
boolean addLabels = true;
|
||||
String labelsPrefix = "";
|
||||
boolean addAnnotations = false;
|
||||
String annotationsPrefix = "";
|
||||
boolean addPorts = false;
|
||||
String portsPrefix = "";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
V1Service service = new V1Service().spec(new V1ServiceSpec().type("ClusterIP"))
|
||||
.metadata(new V1ObjectMeta().namespace("default").labels(Map.of("a", "b")));
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata(properties, service, "my-service");
|
||||
Assertions.assertEquals(result.size(), 3);
|
||||
Assertions.assertEquals(result, Map.of("a", "b", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
String labelsMetadata = filterOnK8sNamespaceAndType(result);
|
||||
Assertions.assertTrue(
|
||||
output.getOut().contains("Adding labels metadata: " + labelsMetadata + " for serviceId: my-service"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - labels are added with prefix
|
||||
* - annotations are not added
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServiceMetadataAddLabelsWithPrefix(CapturedOutput output) {
|
||||
boolean addLabels = true;
|
||||
String labelsPrefix = "prefix-";
|
||||
boolean addAnnotations = false;
|
||||
String annotationsPrefix = "";
|
||||
boolean addPorts = false;
|
||||
String portsPrefix = "";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
V1Service service = new V1Service().spec(new V1ServiceSpec().type("ClusterIP"))
|
||||
.metadata(new V1ObjectMeta().namespace("default").labels(Map.of("a", "b", "c", "d")));
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata(properties, service, "my-service");
|
||||
Assertions.assertEquals(result.size(), 4);
|
||||
Assertions.assertEquals(result,
|
||||
Map.of("prefix-a", "b", "prefix-c", "d", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
// so that result is deterministic in assertion
|
||||
String labelsMetadata = filterOnK8sNamespaceAndType(result);
|
||||
Assertions.assertTrue(
|
||||
output.getOut().contains("Adding labels metadata: " + labelsMetadata + " for serviceId: my-service"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - labels are not added
|
||||
* - annotations are added without prefix
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServiceMetadataAddAnnotationsNoPrefix(CapturedOutput output) {
|
||||
boolean addLabels = false;
|
||||
String labelsPrefix = "";
|
||||
boolean addAnnotations = true;
|
||||
String annotationsPrefix = "";
|
||||
boolean addPorts = false;
|
||||
String portsPrefix = "";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
V1Service service = new V1Service().spec(new V1ServiceSpec().type("ClusterIP")).metadata(
|
||||
new V1ObjectMeta().namespace("default").labels(Map.of("a", "b")).annotations(Map.of("aa", "bb")));
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata(properties, service, "my-service");
|
||||
Assertions.assertEquals(result.size(), 3);
|
||||
Assertions.assertEquals(result, Map.of("aa", "bb", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
Assertions
|
||||
.assertTrue(output.getOut().contains("Adding annotations metadata: {aa=bb} for serviceId: my-service"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - labels are not added
|
||||
* - annotations are added with prefix
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServiceMetadataAddAnnotationsWithPrefix(CapturedOutput output) {
|
||||
boolean addLabels = false;
|
||||
String labelsPrefix = "";
|
||||
boolean addAnnotations = true;
|
||||
String annotationsPrefix = "prefix-";
|
||||
boolean addPorts = false;
|
||||
String portsPrefix = "";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
V1Service service = new V1Service().spec(new V1ServiceSpec().type("ClusterIP")).metadata(new V1ObjectMeta()
|
||||
.namespace("default").labels(Map.of("a", "b")).annotations(Map.of("aa", "bb", "cc", "dd")));
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata(properties, service, "my-service");
|
||||
Assertions.assertEquals(result.size(), 4);
|
||||
Assertions.assertEquals(result,
|
||||
Map.of("prefix-aa", "bb", "prefix-cc", "dd", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
// so that result is deterministic in assertion
|
||||
String annotations = filterOnK8sNamespaceAndType(result);
|
||||
Assertions.assertTrue(
|
||||
output.getOut().contains("Adding annotations metadata: " + annotations + " for serviceId: my-service"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - labels are added with prefix
|
||||
* - annotations are added with prefix
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testServiceMetadataAddLabelsAndAnnotationsWithPrefix(CapturedOutput output) {
|
||||
boolean addLabels = true;
|
||||
String labelsPrefix = "label-";
|
||||
boolean addAnnotations = true;
|
||||
String annotationsPrefix = "annotation-";
|
||||
boolean addPorts = false;
|
||||
String portsPrefix = "";
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(addLabels,
|
||||
labelsPrefix, addAnnotations, annotationsPrefix, addPorts, portsPrefix);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
true, "", Set.of(), Map.of(), "", metadata, 0, false, false);
|
||||
V1Service service = new V1Service().spec(new V1ServiceSpec().type("ClusterIP")).metadata(new V1ObjectMeta()
|
||||
.namespace("default").labels(Map.of("a", "b", "c", "d")).annotations(Map.of("aa", "bb", "cc", "dd")));
|
||||
|
||||
Map<String, String> result = KubernetesDiscoveryClientUtils.serviceMetadata(properties, service, "my-service");
|
||||
Assertions.assertEquals(result.size(), 6);
|
||||
Assertions.assertEquals(result, Map.of("annotation-aa", "bb", "annotation-cc", "dd", "label-a", "b", "label-c",
|
||||
"d", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
// so that result is deterministic in assertion
|
||||
String labels = result.entrySet().stream().filter(en -> en.getKey().contains("label"))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).toString();
|
||||
String annotations = result.entrySet().stream().filter(en -> en.getKey().contains("annotation"))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).toString();
|
||||
Assertions.assertTrue(
|
||||
output.getOut().contains("Adding labels metadata: " + labels + " for serviceId: my-service"));
|
||||
Assertions.assertTrue(
|
||||
output.getOut().contains("Adding annotations metadata: " + annotations + " for serviceId: my-service"));
|
||||
}
|
||||
|
||||
private String filterOnK8sNamespaceAndType(Map<String, String> result) {
|
||||
return result.entrySet().stream().filter(en -> !en.getKey().contains("k8s_namespace"))
|
||||
.filter(en -> !en.getKey().equals("type"))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.discovery;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import io.kubernetes.client.informer.cache.Cache;
|
||||
import io.kubernetes.client.informer.cache.Lister;
|
||||
import io.kubernetes.client.openapi.models.CoreV1EndpointPortBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddressBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointSubsetBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ServicePortBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceSpecBuilder;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class KubernetesDiscoveryClientServiceWithoutPortNameTests {
|
||||
|
||||
private static final String NAMESPACE = "spring-k8s";
|
||||
|
||||
private static final SharedInformerFactoryStub STUB = new SharedInformerFactoryStub();
|
||||
|
||||
private static final SharedInformerStub<V1Service> SERVICE_SHARED_INFORMER_STUB = new SharedInformerStub<>();
|
||||
|
||||
private static final SharedInformerStub<V1Endpoints> ENDPOINTS_SHARED_INFORMER_STUB = new SharedInformerStub<>();
|
||||
|
||||
private Cache<V1Service> servicesCache;
|
||||
|
||||
private Lister<V1Service> servicesLister;
|
||||
|
||||
private Cache<V1Endpoints> endpointsCache;
|
||||
|
||||
private Lister<V1Endpoints> endpointsLister;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
servicesCache = new Cache<>();
|
||||
servicesLister = new Lister<>(servicesCache);
|
||||
|
||||
endpointsCache = new Cache<>();
|
||||
endpointsLister = new Lister<>(endpointsCache);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDiscoveryWithoutAServicePortName() {
|
||||
|
||||
V1Endpoints endpoints = new V1EndpointsBuilder()
|
||||
.withSubsets(
|
||||
new V1EndpointSubsetBuilder().withPorts(new CoreV1EndpointPortBuilder().withPort(8080).build())
|
||||
.withAddresses(new V1EndpointAddressBuilder().withIp("127.0.0.1").build()).build())
|
||||
.withMetadata(
|
||||
new V1ObjectMetaBuilder().withName("no-port-name-service").withNamespace(NAMESPACE).build())
|
||||
.build();
|
||||
endpointsCache.add(endpoints);
|
||||
|
||||
V1Service service = new V1ServiceBuilder()
|
||||
.withSpec(
|
||||
new V1ServiceSpecBuilder().withPorts(new V1ServicePortBuilder().withPort(8080).build()).build())
|
||||
.withMetadata(
|
||||
new V1ObjectMetaBuilder().withName("no-port-name-service").withNamespace(NAMESPACE).build())
|
||||
.withSpec(new V1ServiceSpecBuilder().withType("ClusterIP").build()).build();
|
||||
servicesCache.add(service);
|
||||
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(NAMESPACE),
|
||||
true, 60, false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0,
|
||||
true);
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("no-port-name-service");
|
||||
Assertions.assertEquals(serviceInstances.size(), 1);
|
||||
Assertions.assertEquals(serviceInstances.get(0).getMetadata(),
|
||||
Map.of("port.<unset>", "8080", "k8s_namespace", "spring-k8s", "type", "ClusterIP"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.discovery;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import io.kubernetes.client.informer.cache.Cache;
|
||||
import io.kubernetes.client.informer.cache.Lister;
|
||||
import io.kubernetes.client.openapi.models.CoreV1EndpointPort;
|
||||
import io.kubernetes.client.openapi.models.CoreV1EndpointPortBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddress;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddressBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointSubset;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceSpecBuilder;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesServiceInstance;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
class KubernetesDiscoveryClientTests {
|
||||
|
||||
private static final SharedInformerFactoryStub STUB = new SharedInformerFactoryStub();
|
||||
|
||||
private static final SharedInformerStub<V1Service> SERVICE_SHARED_INFORMER_STUB = new SharedInformerStub<>();
|
||||
|
||||
private static final SharedInformerStub<V1Endpoints> ENDPOINTS_SHARED_INFORMER_STUB = new SharedInformerStub<>();
|
||||
|
||||
private Cache<V1Service> servicesCache;
|
||||
|
||||
private Lister<V1Service> servicesLister;
|
||||
|
||||
private Cache<V1Endpoints> endpointsCache;
|
||||
|
||||
private Lister<V1Endpoints> endpointsLister;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
servicesCache = new Cache<>();
|
||||
servicesLister = new Lister<>(servicesCache);
|
||||
|
||||
endpointsCache = new Cache<>();
|
||||
endpointsLister = new Lister<>(endpointsCache);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInstancesShouldBeAbleToHandleEndpointsSingleAddress() {
|
||||
Map<String, String> labels = Map.of("l", "v");
|
||||
String serviceId = "id";
|
||||
String serviceType = "ExternalName";
|
||||
String namespace = "test";
|
||||
List<String> ips = List.of("ip1");
|
||||
List<String> uuids = List.of("10");
|
||||
List<String> names = List.of("http");
|
||||
List<String> protocols = List.of("TCP");
|
||||
List<Integer> ports = List.of(80);
|
||||
List<String> appProtocols = List.of("appTCP");
|
||||
|
||||
setup(serviceId, serviceType, namespace, labels, ips, uuids, names, protocols, ports, appProtocols);
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, null, false,
|
||||
null, false, null);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
|
||||
false, null, Set.of(), Map.of(), null, metadata, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("id");
|
||||
|
||||
assertThat(instances).hasSize(1).filteredOn(s -> s.getHost().equals("ip1") && !s.isSecure()).hasSize(1)
|
||||
.filteredOn(s -> s.getInstanceId().equals("10")).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInstancesShouldBeAbleToHandleEndpointsSingleAddressAndMultiplePorts() {
|
||||
Map<String, String> labels = Map.of("l2", "v2");
|
||||
String serviceId = "endpoint";
|
||||
String serviceType = "ExternalName";
|
||||
String namespace = "test";
|
||||
List<String> ips = List.of("ip1");
|
||||
List<String> uuids = List.of("20");
|
||||
List<String> names = List.of("http", "mgmt");
|
||||
List<String> protocols = List.of("TCP", "TCP");
|
||||
List<Integer> ports = List.of(80, 900);
|
||||
List<String> appProtocols = List.of("http_tcp", "mgmt_tcp");
|
||||
|
||||
setup(serviceId, serviceType, namespace, labels, ips, uuids, names, protocols, ports, appProtocols);
|
||||
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
|
||||
false, null, Set.of(), labels, "http_tcp", KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint");
|
||||
|
||||
assertThat(instances).hasSize(1).filteredOn(s -> s.getHost().equals("ip1") && !s.isSecure()).hasSize(1)
|
||||
.filteredOn(s -> s.getInstanceId().equals("20")).hasSize(1).filteredOn(s -> 80 == s.getPort())
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInstancesShouldBeAbleToHandleEndpointsMultipleAddresses() {
|
||||
Map<String, String> labels = Map.of("l1", "v1");
|
||||
String serviceId = "endpoint";
|
||||
String serviceType = "ExternalName";
|
||||
String namespace = "test";
|
||||
List<String> ips = List.of("ip1", "ip2");
|
||||
List<String> uuids = List.of("40", "50");
|
||||
List<String> names = List.of("https");
|
||||
List<String> protocols = List.of("TCP");
|
||||
List<Integer> ports = List.of(443);
|
||||
List<String> appProtocols = List.of("https_tcp");
|
||||
|
||||
setup(serviceId, serviceType, namespace, labels, ips, uuids, names, protocols, ports, appProtocols);
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, null, false,
|
||||
null, true, "port.");
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
|
||||
false, null, Set.of(443, 8443), labels, null, metadata, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint");
|
||||
|
||||
assertThat(instances).hasSize(2).filteredOn(ServiceInstance::isSecure).extracting(ServiceInstance::getHost)
|
||||
.containsOnly("ip1", "ip2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInstancesShouldBeAbleToHandleEndpointsFromMultipleNamespaces() {
|
||||
|
||||
Map<String, String> labels = Map.of("l", "v");
|
||||
String serviceId = "endpoint";
|
||||
String serviceType = "ExternalName";
|
||||
String namespace = "test";
|
||||
List<String> ips = List.of("ip1");
|
||||
List<String> uuids = List.of("60");
|
||||
List<String> names = List.of("http");
|
||||
List<String> protocols = List.of("TCP");
|
||||
List<Integer> ports = List.of(80);
|
||||
List<String> appProtocols = List.of("https_tcp");
|
||||
|
||||
setup(serviceId, serviceType, namespace, labels, ips, uuids, names, protocols, ports, appProtocols);
|
||||
|
||||
ips = List.of("ip2");
|
||||
uuids = List.of("70");
|
||||
namespace = "test2";
|
||||
|
||||
setup(serviceId, serviceType, namespace, labels, ips, uuids, names, protocols, ports, appProtocols);
|
||||
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
|
||||
false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint");
|
||||
|
||||
assertThat(instances).hasSize(2);
|
||||
assertThat(instances).filteredOn(s -> s.getHost().equals("ip1") && !s.isSecure()).hasSize(1);
|
||||
assertThat(instances).filteredOn(s -> s.getHost().equals("ip2") && !s.isSecure()).hasSize(1);
|
||||
assertThat(instances).filteredOn(s -> s.getServiceId().contains("endpoint")
|
||||
&& ((KubernetesServiceInstance) s).getNamespace().equals("test")).hasSize(1);
|
||||
assertThat(instances).filteredOn(s -> s.getServiceId().contains("endpoint")
|
||||
&& ((KubernetesServiceInstance) s).getNamespace().equals("test2")).hasSize(1);
|
||||
assertThat(instances).filteredOn(s -> s.getInstanceId().equals("60")).hasSize(1);
|
||||
assertThat(instances).filteredOn(s -> s.getInstanceId().equals("70")).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceWithoutSubsetsShouldBeSkipped() {
|
||||
V1Endpoints endpoints = new V1EndpointsBuilder().withNewMetadata().withName("endpoint1").withNamespace("test")
|
||||
.withLabels(Collections.emptyMap()).endMetadata().build();
|
||||
endpointsCache.add(endpoints);
|
||||
|
||||
V1Service service = new V1ServiceBuilder().withNewMetadata().withName("endpoint1").withNamespace("test").and()
|
||||
.build();
|
||||
servicesCache.add(service);
|
||||
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
|
||||
false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, true);
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint1");
|
||||
|
||||
assertThat(instances).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInstancesShouldBeAbleToHandleEndpointsSingleAddressAndMultiplePortsUsingPrimaryPortNameLabel() {
|
||||
Map<String, String> labels = Map.of("primary-port-name", "https");
|
||||
String serviceId = "endpoint2";
|
||||
String serviceType = "ExternalName";
|
||||
String namespace = "test";
|
||||
List<String> ips = List.of("ip1");
|
||||
List<String> uuids = List.of("80");
|
||||
List<String> names = List.of("http", "https");
|
||||
List<String> protocols = List.of("TCP", "TCP");
|
||||
List<Integer> ports = List.of(80, 443);
|
||||
List<String> appProtocols = List.of("http", "https");
|
||||
|
||||
setup(serviceId, serviceType, namespace, labels, ips, uuids, names, protocols, ports, appProtocols);
|
||||
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
|
||||
false, null, Set.of(443, 8443), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0,
|
||||
true);
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint2");
|
||||
|
||||
assertThat(instances).hasSize(1).filteredOn(s -> s.getHost().equals("ip1") && s.isSecure()).hasSize(1)
|
||||
.filteredOn(s -> s.getInstanceId().equals("80")).hasSize(1).filteredOn(s -> 443 == s.getPort())
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceWithMultiplePortsAndMisconfiguredPrimaryPortNameInLabelWithoutFallbackShouldLogWarning() {
|
||||
Map<String, String> labels = Map.of("primary-port-name", "oops");
|
||||
String serviceId = "endpoint3";
|
||||
String serviceType = "ExternalName";
|
||||
String namespace = "test";
|
||||
List<String> ips = List.of("ip1");
|
||||
List<String> uuids = List.of("90");
|
||||
List<String> names = List.of("httpA", "httpB", "httpC", "httpD");
|
||||
List<String> protocols = List.of("TCP", "TCP", "TCP", "TCP");
|
||||
List<Integer> ports = List.of(8443, 443, 80, 8080);
|
||||
List<String> appProtocols = List.of("https1", "https2", "http1", "http2");
|
||||
|
||||
setup(serviceId, serviceType, namespace, labels, ips, uuids, names, protocols, ports, appProtocols);
|
||||
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
|
||||
false, null, Set.of(443, 8443), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0,
|
||||
true);
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint3");
|
||||
|
||||
assertThat(instances).hasSize(1).filteredOn(s -> s.getHost().equals("ip1") && s.isSecure()).hasSize(1)
|
||||
.filteredOn(s -> s.getInstanceId().equals("90")).hasSize(1).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceWithMultiplePortsAndMisconfiguredGenericPrimaryPortNameWithoutFallbackShouldLogWarning() {
|
||||
Map<String, String> labels = Map.of();
|
||||
String serviceId = "endpoint4";
|
||||
String serviceType = "ExternalName";
|
||||
String namespace = "test";
|
||||
List<String> ips = List.of("ip1");
|
||||
List<String> uuids = List.of("100");
|
||||
List<String> names = List.of("httpA", "httpB", "httpC", "httpD");
|
||||
List<String> protocols = List.of("TCP", "TCP", "TCP", "TCP");
|
||||
List<Integer> ports = List.of(8443, 443, 80, 8080);
|
||||
List<String> appProtocols = List.of("https1", "https2", "http1", "http2");
|
||||
|
||||
setup(serviceId, serviceType, namespace, labels, ips, uuids, names, protocols, ports, appProtocols);
|
||||
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
|
||||
false, null, Set.of(443, 8443), Map.of(), "oops", KubernetesDiscoveryProperties.Metadata.DEFAULT, 0,
|
||||
true);
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint4");
|
||||
|
||||
assertThat(instances).hasSize(1).filteredOn(s -> s.getHost().equals("ip1") && s.isSecure()).hasSize(1)
|
||||
.filteredOn(s -> s.getInstanceId().equals("100")).hasSize(1).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceWithMultiplePortsAndWithoutPrimaryPortNameSpecifiedShouldFallBackToHttps() {
|
||||
Map<String, String> labels = Map.of();
|
||||
String serviceId = "endpoint5";
|
||||
String serviceType = "ExternalName";
|
||||
String namespace = "test";
|
||||
List<String> ips = List.of("ip1");
|
||||
List<String> uuids = List.of("110");
|
||||
List<String> names = List.of("httpA", "httpB");
|
||||
List<String> protocols = List.of("TCP", "TCP");
|
||||
List<Integer> ports = List.of(443, 80);
|
||||
List<String> appProtocols = List.of("http", "https");
|
||||
|
||||
setup(serviceId, serviceType, namespace, labels, ips, uuids, names, protocols, ports, appProtocols);
|
||||
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of(), true, 60,
|
||||
false, null, Set.of(443, 8443), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0,
|
||||
true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint5");
|
||||
|
||||
assertThat(instances).hasSize(1).filteredOn(s -> s.getHost().equals("ip1") && s.isSecure()).hasSize(1)
|
||||
.filteredOn(s -> s.getInstanceId().equals("110")).hasSize(1).filteredOn(s -> 443 == s.getPort())
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceWithMultiplePortsAndWithoutPrimaryPortNameSpecifiedOrHttpsPortShouldFallBackToHttp() {
|
||||
Map<String, String> labels = Map.of();
|
||||
String serviceId = "endpoint5";
|
||||
String serviceType = "ExternalName";
|
||||
String namespace = "test";
|
||||
List<String> ips = List.of("ip1");
|
||||
List<String> uuids = List.of("120");
|
||||
List<String> names = List.of("httpA", "httpB", "httpC");
|
||||
List<String> protocols = List.of("http", "http", "http");
|
||||
List<Integer> ports = List.of(80, 8443, 80);
|
||||
List<String> appProtocols = List.of("TCP", "TCP", "TCP");
|
||||
|
||||
setup(serviceId, serviceType, namespace, labels, ips, uuids, names, protocols, ports, appProtocols);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB,
|
||||
KubernetesDiscoveryProperties.DEFAULT);
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint5");
|
||||
|
||||
assertThat(instances).hasSize(1).filteredOn(s -> s.getHost().equals("ip1") && !s.isSecure()).hasSize(1)
|
||||
.filteredOn(s -> s.getInstanceId().equals("120")).hasSize(1).filteredOn(s -> 80 == s.getPort())
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceWithMultiplePortsAndWithoutPrimaryPortNameSpecifiedShouldLogWarning() {
|
||||
Map<String, String> labels = Map.of();
|
||||
String serviceId = "endpoint5";
|
||||
String serviceType = "ExternalName";
|
||||
String namespace = "test";
|
||||
List<String> ips = List.of("ip1");
|
||||
List<String> uuids = List.of("130");
|
||||
List<String> names = List.of("http", "https");
|
||||
List<String> protocols = List.of("http", "https");
|
||||
List<Integer> ports = List.of(80, 443);
|
||||
List<String> appProtocols = List.of("TCP", "TCP");
|
||||
|
||||
setup(serviceId, serviceType, namespace, labels, ips, uuids, names, protocols, ports, appProtocols);
|
||||
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
|
||||
true, null, Set.of(443, 8443), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint5");
|
||||
|
||||
// We're returning the first discovered port to not change previous behaviour
|
||||
assertThat(instances).hasSize(1).filteredOn(s -> s.getHost().equals("ip1") && s.isSecure()).hasSize(1)
|
||||
.filteredOn(s -> s.getInstanceId().equals("130")).hasSize(1).filteredOn(s -> 443 == s.getPort())
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void instanceWithoutPorts() {
|
||||
Map<String, String> labels = Map.of();
|
||||
String serviceId = "endpoint5";
|
||||
String serviceType = "ExternalName";
|
||||
String namespace = "test";
|
||||
List<String> ips = List.of("ip1");
|
||||
List<String> uuids = List.of("130");
|
||||
List<String> names = List.of();
|
||||
List<String> protocols = List.of();
|
||||
List<Integer> ports = List.of();
|
||||
List<String> appProtocols = List.of();
|
||||
|
||||
setup(serviceId, serviceType, namespace, labels, ips, uuids, names, protocols, ports, appProtocols);
|
||||
|
||||
KubernetesDiscoveryProperties properties = KubernetesDiscoveryProperties.DEFAULT;
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(STUB, servicesLister,
|
||||
endpointsLister, SERVICE_SHARED_INFORMER_STUB, ENDPOINTS_SHARED_INFORMER_STUB, properties);
|
||||
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances("endpoint5");
|
||||
|
||||
// We're returning the first discovered port to not change previous behaviour
|
||||
assertThat(instances).hasSize(1).filteredOn(s -> s.getHost().equals("ip1") && !s.isSecure()).hasSize(1)
|
||||
.filteredOn(s -> s.getUri().toASCIIString().equals("http://ip1"))
|
||||
.filteredOn(s -> s.getInstanceId().equals("130")).hasSize(1).filteredOn(s -> 0 == s.getPort())
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
private void setup(String serviceId, String serviceType, String namespace, Map<String, String> labels,
|
||||
List<String> ips, List<String> uuids, List<String> names, List<String> protocols, List<Integer> ports,
|
||||
List<String> appProtocols) {
|
||||
|
||||
V1Service service = new V1ServiceBuilder().withSpec(new V1ServiceSpecBuilder().withType(serviceType).build())
|
||||
.withNewMetadata().withName(serviceId).withNamespace(namespace).withLabels(labels).endMetadata()
|
||||
.build();
|
||||
|
||||
servicesCache.add(service);
|
||||
|
||||
V1ObjectMeta objectMeta = new V1ObjectMeta();
|
||||
objectMeta.setNamespace(namespace);
|
||||
objectMeta.setName(serviceId);
|
||||
|
||||
V1Endpoints endpoints = new V1EndpointsBuilder().withNewMetadata().withName(serviceId).withNamespace(namespace)
|
||||
.withLabels(labels).endMetadata().build();
|
||||
|
||||
List<V1EndpointAddress> addresses = new ArrayList<>();
|
||||
for (int i = 0; i < ips.size(); ++i) {
|
||||
V1EndpointAddress address = new V1EndpointAddressBuilder().withIp(ips.get(i)).withNewTargetRef()
|
||||
.withUid(uuids.get(i)).endTargetRef().build();
|
||||
addresses.add(address);
|
||||
}
|
||||
|
||||
V1EndpointSubset subset = new V1EndpointSubset();
|
||||
subset.setAddresses(addresses);
|
||||
|
||||
List<CoreV1EndpointPort> corePorts = new ArrayList<>();
|
||||
for (int i = 0; i < names.size(); ++i) {
|
||||
CoreV1EndpointPort port = new CoreV1EndpointPortBuilder().withName(names.get(i))
|
||||
.withProtocol(protocols.get(i)).withPort(ports.get(i)).withAppProtocol(appProtocols.get(i)).build();
|
||||
corePorts.add(port);
|
||||
}
|
||||
subset.setPorts(corePorts);
|
||||
endpoints.setSubsets(List.of(subset));
|
||||
|
||||
endpointsCache.add(endpoints);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,10 +17,16 @@
|
||||
package org.springframework.cloud.kubernetes.client.discovery;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.kubernetes.client.openapi.models.CoreV1EndpointPortBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddress;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddressBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointSubset;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointSubsetBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceBuilder;
|
||||
@@ -32,6 +38,7 @@ import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
|
||||
import static org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientUtils.endpointSubsetsPortData;
|
||||
import static org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientUtils.matchesServiceLabels;
|
||||
|
||||
/**
|
||||
@@ -161,6 +168,139 @@ class KubernetesDiscoveryClientUtilsTests {
|
||||
Assertions.assertTrue(output.getOut().contains("Service labels from service : {a=b, c=d}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPortsDataOne() {
|
||||
List<V1EndpointSubset> endpointSubsets = List.of(
|
||||
new V1EndpointSubsetBuilder()
|
||||
.withPorts(new CoreV1EndpointPortBuilder().withPort(8081).withName("").build()).build(),
|
||||
new V1EndpointSubsetBuilder()
|
||||
.withPorts(new CoreV1EndpointPortBuilder().withPort(8080).withName("https").build()).build());
|
||||
|
||||
Map<String, Integer> portsData = endpointSubsetsPortData(endpointSubsets);
|
||||
Assertions.assertEquals(portsData.size(), 2);
|
||||
Assertions.assertEquals(portsData.get("https"), 8080);
|
||||
Assertions.assertEquals(portsData.get("<unset>"), 8081);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPortsDataTwo() {
|
||||
List<V1EndpointSubset> endpointSubsets = List.of(
|
||||
new V1EndpointSubsetBuilder()
|
||||
.withPorts(new CoreV1EndpointPortBuilder().withPort(8081).withName("http").build()).build(),
|
||||
new V1EndpointSubsetBuilder()
|
||||
.withPorts(new CoreV1EndpointPortBuilder().withPort(8080).withName("https").build()).build());
|
||||
|
||||
Map<String, Integer> portsData = endpointSubsetsPortData(endpointSubsets);
|
||||
Assertions.assertEquals(portsData.size(), 2);
|
||||
Assertions.assertEquals(portsData.get("https"), 8080);
|
||||
Assertions.assertEquals(portsData.get("http"), 8081);
|
||||
}
|
||||
|
||||
@Test
|
||||
void endpointSubsetPortsDataWithoutPorts() {
|
||||
V1EndpointSubset endpointSubset = new V1EndpointSubsetBuilder().build();
|
||||
Map<String, Integer> result = endpointSubsetsPortData(List.of(endpointSubset));
|
||||
|
||||
Assertions.assertEquals(result.size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void endpointSubsetPortsDataSinglePort() {
|
||||
V1EndpointSubset endpointSubset = new V1EndpointSubsetBuilder()
|
||||
.withPorts(new CoreV1EndpointPortBuilder().withName("name").withPort(80).build()).build();
|
||||
Map<String, Integer> result = endpointSubsetsPortData(List.of(endpointSubset));
|
||||
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result.get("name"), 80);
|
||||
}
|
||||
|
||||
@Test
|
||||
void endpointSubsetPortsDataSinglePortNoName() {
|
||||
V1EndpointSubset endpointSubset = new V1EndpointSubsetBuilder()
|
||||
.withPorts(new CoreV1EndpointPortBuilder().withPort(80).build()).build();
|
||||
Map<String, Integer> result = endpointSubsetsPortData(List.of(endpointSubset));
|
||||
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result.get("<unset>"), 80);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - ready addresses are empty
|
||||
* - not ready addresses are not included
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testEmptyAddresses() {
|
||||
boolean includeNotReadyAddresses = false;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
includeNotReadyAddresses, "", Set.of(), Map.of(), "", null, 0, false, false);
|
||||
V1EndpointSubset endpointSubset = new V1EndpointSubsetBuilder().build();
|
||||
List<V1EndpointAddress> addresses = KubernetesDiscoveryClientUtils.addresses(endpointSubset, properties);
|
||||
Assertions.assertEquals(addresses.size(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - ready addresses has two entries
|
||||
* - not ready addresses are not included
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testReadyAddressesOnly() {
|
||||
boolean includeNotReadyAddresses = false;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
includeNotReadyAddresses, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
V1EndpointSubset endpointSubset = new V1EndpointSubsetBuilder()
|
||||
.withAddresses(new V1EndpointAddressBuilder().withHostname("one").build(),
|
||||
new V1EndpointAddressBuilder().withHostname("two").build())
|
||||
.build();
|
||||
List<V1EndpointAddress> addresses = KubernetesDiscoveryClientUtils.addresses(endpointSubset, properties);
|
||||
Assertions.assertEquals(addresses.size(), 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - ready addresses has two entries
|
||||
* - not ready addresses has a single entry, but we do not take it
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testReadyAddressesTakenNotReadyAddressesNotTaken() {
|
||||
boolean includeNotReadyAddresses = false;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
includeNotReadyAddresses, "", Set.of(), Map.of(), "", null, 0, false, false);
|
||||
V1EndpointSubset endpointSubset = new V1EndpointSubsetBuilder()
|
||||
.withAddresses(new V1EndpointAddressBuilder().withHostname("one").build(),
|
||||
new V1EndpointAddressBuilder().withHostname("two").build())
|
||||
.withNotReadyAddresses(new V1EndpointAddressBuilder().withHostname("three").build()).build();
|
||||
List<V1EndpointAddress> addresses = KubernetesDiscoveryClientUtils.addresses(endpointSubset, properties);
|
||||
Assertions.assertEquals(addresses.size(), 2);
|
||||
List<String> hostNames = addresses.stream().map(V1EndpointAddress::getHostname).sorted().toList();
|
||||
Assertions.assertEquals(hostNames, List.of("one", "two"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - ready addresses has two entries
|
||||
* - not ready addresses has a single entry, but we do not take it
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void testBothAddressesTaken() {
|
||||
boolean includeNotReadyAddresses = true;
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L,
|
||||
includeNotReadyAddresses, "", Set.of(), Map.of(), "", null, 0, false);
|
||||
V1EndpointSubset endpointSubset = new V1EndpointSubsetBuilder()
|
||||
.withAddresses(new V1EndpointAddressBuilder().withHostname("one").build(),
|
||||
new V1EndpointAddressBuilder().withHostname("two").build())
|
||||
.withNotReadyAddresses(new V1EndpointAddressBuilder().withHostname("three").build()).build();
|
||||
List<V1EndpointAddress> addresses = KubernetesDiscoveryClientUtils.addresses(endpointSubset, properties);
|
||||
Assertions.assertEquals(addresses.size(), 3);
|
||||
List<String> hostNames = addresses.stream().map(V1EndpointAddress::getHostname).sorted().toList();
|
||||
Assertions.assertEquals(hostNames, List.of("one", "three", "two"));
|
||||
}
|
||||
|
||||
// preserve order for testing reasons
|
||||
private Map<String, String> ordered(Map<String, String> input) {
|
||||
return input.entrySet().stream().sorted(Map.Entry.comparingByKey()).collect(
|
||||
|
||||
@@ -21,16 +21,33 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
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.ApiClient;
|
||||
import io.kubernetes.client.openapi.JSON;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.models.CoreV1EndpointPort;
|
||||
import io.kubernetes.client.openapi.models.CoreV1EndpointPortBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddress;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddressBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointSubset;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointSubsetBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointsBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ObjectReferenceBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Pod;
|
||||
import io.kubernetes.client.openapi.models.V1PodBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceSpec;
|
||||
import io.kubernetes.client.openapi.models.V1ServiceSpecBuilder;
|
||||
import io.kubernetes.client.util.ClientBuilder;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@@ -38,6 +55,7 @@ import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class KubernetesInformerDiscoveryClientTests {
|
||||
@@ -89,8 +107,8 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, ALL_NAMESPACES);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1").toArray())
|
||||
.containsOnly(new DefaultKubernetesServiceInstance("", "test-svc-1", "1.1.1.1", 80,
|
||||
Map.of("<unset>", "80", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
.containsOnly(new DefaultKubernetesServiceInstance(null, "test-svc-1", "1.1.1.1", 80,
|
||||
Map.of("port.<unset>", "80", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
"namespace1", null));
|
||||
}
|
||||
|
||||
@@ -136,11 +154,10 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1").toArray()).isEmpty();
|
||||
assertThat(discoveryClient.getInstances("test-svc-3").toArray())
|
||||
.containsOnly(
|
||||
new DefaultKubernetesServiceInstance(
|
||||
"", "test-svc-3", "2.2.2.2", 8080, Map.of("spring", "true", "<unset>", "8080", "k8s",
|
||||
"true", "k8s_namespace", "namespace1", "type", "ClusterIP"),
|
||||
false, "namespace1", null));
|
||||
.containsOnly(new DefaultKubernetesServiceInstance(
|
||||
null, "test-svc-3", "2.2.2.2", 8080, Map.of("spring", "true", "port.<unset>", "8080", "k8s",
|
||||
"true", "k8s_namespace", "namespace1", "type", "ClusterIP"),
|
||||
false, "namespace1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -188,8 +205,8 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, ALL_NAMESPACES);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1"))
|
||||
.containsOnly(new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("<unset>", "8080", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
.containsOnly(new DefaultKubernetesServiceInstance(null, "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("port.<unset>", "8080", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
"namespace1", null));
|
||||
}
|
||||
|
||||
@@ -202,8 +219,8 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, NOT_ALL_NAMESPACES);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1"))
|
||||
.containsOnly(new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("<unset>", "8080", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
.containsOnly(new DefaultKubernetesServiceInstance(null, "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("port.<unset>", "8080", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
"namespace1", null));
|
||||
}
|
||||
|
||||
@@ -232,8 +249,8 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1"))
|
||||
.containsOnly(new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("<unset>", "8080", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
.containsOnly(new DefaultKubernetesServiceInstance(null, "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("port.<unset>", "8080", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
"namespace1", null));
|
||||
}
|
||||
|
||||
@@ -250,7 +267,7 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceWithoutPortsShouldBeSkipped() {
|
||||
void instanceWithoutPortsWillNotBeSkipped() {
|
||||
Lister<V1Service> serviceLister = setupServiceLister(SERVICE_1);
|
||||
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(ENDPOINTS_NO_PORTS);
|
||||
|
||||
@@ -258,7 +275,9 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null,
|
||||
KubernetesDiscoveryProperties.DEFAULT);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1")).isEmpty();
|
||||
assertThat(discoveryClient.getInstances("test-svc-1"))
|
||||
.containsOnly(new DefaultKubernetesServiceInstance(null, "test-svc-1", "1.1.1.1", 0,
|
||||
Map.of("k8s_namespace", "namespace1", "type", "ClusterIP"), false, "namespace1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -269,12 +288,10 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, NOT_ALL_NAMESPACES);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1"))
|
||||
.containsOnly(
|
||||
new DefaultKubernetesServiceInstance(
|
||||
"", "test-svc-1", "1.1.1.1", 443, Map.of("http", "80", "primary-port-name", "https",
|
||||
"https", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"),
|
||||
false, "namespace1", null));
|
||||
assertThat(discoveryClient.getInstances("test-svc-1")).containsOnly(new DefaultKubernetesServiceInstance(
|
||||
null, "test-svc-1", "1.1.1.1", 443, Map.of("port.http", "80", "primary-port-name", "https",
|
||||
"port.https", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"),
|
||||
true, "namespace1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -287,11 +304,10 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, NOT_ALL_NAMESPACES);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1"))
|
||||
.containsOnly(
|
||||
new DefaultKubernetesServiceInstance(
|
||||
"", "test-svc-1", "1.1.1.1", 80, Map.of("tcp1", "80", "primary-port-name", "oops",
|
||||
"tcp2", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"),
|
||||
false, "namespace1", null));
|
||||
.containsOnly(new DefaultKubernetesServiceInstance(
|
||||
null, "test-svc-1", "1.1.1.1", 80, Map.of("port.tcp1", "80", "primary-port-name", "oops",
|
||||
"port.tcp2", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"),
|
||||
false, "namespace1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -302,10 +318,10 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, NOT_ALL_NAMESPACES);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1"))
|
||||
.containsOnly(new DefaultKubernetesServiceInstance("", "test-svc-1", "1.1.1.1", 443,
|
||||
Map.of("http", "80", "https", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
"namespace1", null));
|
||||
assertThat(discoveryClient.getInstances("test-svc-1")).containsOnly(new DefaultKubernetesServiceInstance(null,
|
||||
"test-svc-1", "1.1.1.1", 443,
|
||||
Map.of("port.http", "80", "port.https", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"),
|
||||
true, "namespace1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -317,10 +333,10 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, NOT_ALL_NAMESPACES);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1"))
|
||||
.containsOnly(new DefaultKubernetesServiceInstance("", "test-svc-1", "1.1.1.1", 80,
|
||||
Map.of("tcp1", "80", "tcp2", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
"namespace1", null));
|
||||
assertThat(discoveryClient.getInstances("test-svc-1")).containsOnly(new DefaultKubernetesServiceInstance(null,
|
||||
"test-svc-1", "1.1.1.1", 80,
|
||||
Map.of("port.tcp1", "80", "port.tcp2", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"),
|
||||
false, "namespace1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -331,10 +347,10 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, NOT_ALL_NAMESPACES);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1"))
|
||||
.containsOnly(new DefaultKubernetesServiceInstance("", "test-svc-1", "1.1.1.1", 443,
|
||||
Map.of("http", "80", "https", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
"namespace1", null));
|
||||
assertThat(discoveryClient.getInstances("test-svc-1")).containsOnly(new DefaultKubernetesServiceInstance(null,
|
||||
"test-svc-1", "1.1.1.1", 443,
|
||||
Map.of("port.http", "80", "port.https", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"),
|
||||
true, "namespace1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -345,10 +361,10 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, NOT_ALL_NAMESPACES);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1"))
|
||||
.containsOnly(new DefaultKubernetesServiceInstance("", "test-svc-1", "1.1.1.1", 80,
|
||||
Map.of("http", "80", "tcp", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
"namespace1", null));
|
||||
assertThat(discoveryClient.getInstances("test-svc-1")).containsOnly(new DefaultKubernetesServiceInstance(null,
|
||||
"test-svc-1", "1.1.1.1", 80,
|
||||
Map.of("port.http", "80", "port.tcp", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
"namespace1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -360,10 +376,10 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, NOT_ALL_NAMESPACES);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1"))
|
||||
.containsOnly(new DefaultKubernetesServiceInstance("", "test-svc-1", "1.1.1.1", 80,
|
||||
Map.of("tcp1", "80", "tcp2", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
"namespace1", null));
|
||||
assertThat(discoveryClient.getInstances("test-svc-1")).containsOnly(new DefaultKubernetesServiceInstance(null,
|
||||
"test-svc-1", "1.1.1.1", 80,
|
||||
Map.of("port.tcp1", "80", "port.tcp2", "443", "k8s_namespace", "namespace1", "type", "ClusterIP"),
|
||||
false, "namespace1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -375,11 +391,11 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, ALL_NAMESPACES);
|
||||
|
||||
assertThat(discoveryClient.getInstances("test-svc-1")).containsOnly(
|
||||
new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("<unset>", "8080", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
new DefaultKubernetesServiceInstance(null, "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("port.<unset>", "8080", "k8s_namespace", "namespace1", "type", "ClusterIP"), false,
|
||||
"namespace1", null),
|
||||
new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("<unset>", "8080", "k8s_namespace", "namespace2", "type", "ClusterIP"), false,
|
||||
new DefaultKubernetesServiceInstance(null, "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("port.<unset>", "8080", "k8s_namespace", "namespace2", "type", "ClusterIP"), false,
|
||||
"namespace2", null));
|
||||
}
|
||||
|
||||
@@ -496,6 +512,94 @@ class KubernetesInformerDiscoveryClientTests {
|
||||
assertThat(serviceInstances.get(1).getMetadata().get("k8s_namespace")).isEqualTo("namespaceB");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExternalNameService() {
|
||||
V1Service externalNameService = new V1ServiceBuilder()
|
||||
.withSpec(new V1ServiceSpecBuilder().withType("ExternalName").withExternalName("k8s-spring-b").build())
|
||||
.withNewMetadata().withLabels(Map.of("label-key", "label-value")).withAnnotations(Map.of("abc", "def"))
|
||||
.withName("blue-service").withNamespace("b").endMetadata().build();
|
||||
|
||||
V1Endpoints endpoints = new V1EndpointsBuilder().withMetadata(new V1ObjectMeta().namespace("irrelevant"))
|
||||
.build();
|
||||
|
||||
Lister<V1Service> serviceLister = setupServiceLister(externalNameService);
|
||||
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(endpoints);
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(true,
|
||||
"labels-prefix-", true, "annotations-prefix-", true, "ports-prefix");
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of("a", "b"), true,
|
||||
60L, false, "", Set.of(), Map.of(), "", metadata, 0, false, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, properties);
|
||||
|
||||
List<ServiceInstance> result = discoveryClient.getInstances("blue-service");
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
DefaultKubernetesServiceInstance externalNameServiceInstance = (DefaultKubernetesServiceInstance) result.get(0);
|
||||
Assertions.assertEquals(externalNameServiceInstance.getServiceId(), "blue-service");
|
||||
Assertions.assertEquals(externalNameServiceInstance.getHost(), "k8s-spring-b");
|
||||
Assertions.assertEquals(externalNameServiceInstance.getPort(), -1);
|
||||
Assertions.assertFalse(externalNameServiceInstance.isSecure());
|
||||
Assertions.assertEquals(externalNameServiceInstance.getUri().toASCIIString(), "k8s-spring-b");
|
||||
Assertions.assertEquals(externalNameServiceInstance.getMetadata(), Map.of("k8s_namespace", "b",
|
||||
"labels-prefix-label-key", "label-value", "annotations-prefix-abc", "def", "type", "ExternalName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPodMetadata() {
|
||||
V1Service nonExternalNameService = new V1ServiceBuilder()
|
||||
.withSpec(new V1ServiceSpecBuilder().withType("ClusterIP").build()).withNewMetadata()
|
||||
.withName("blue-service").withNamespace("a").endMetadata().build();
|
||||
|
||||
V1Endpoints endpoints = new V1EndpointsBuilder()
|
||||
.withMetadata(new V1ObjectMetaBuilder().withName("blue-service").withNamespace("a").build())
|
||||
.withSubsets(
|
||||
new V1EndpointSubsetBuilder().withPorts(new CoreV1EndpointPortBuilder().withPort(8080).build())
|
||||
.withAddresses(new V1EndpointAddressBuilder().withIp("127.0.0.1").withTargetRef(
|
||||
new V1ObjectReferenceBuilder().withKind("Pod").withName("my-pod").build())
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
Lister<V1Service> serviceLister = setupServiceLister(nonExternalNameService);
|
||||
Lister<V1Endpoints> endpointsLister = setupEndpointsLister(endpoints);
|
||||
|
||||
WireMockServer server = new WireMockServer(options().dynamicPort());
|
||||
server.start();
|
||||
WireMock.configureFor("localhost", server.port());
|
||||
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + server.port()).build();
|
||||
|
||||
V1Pod pod = new V1PodBuilder().withNewMetadata().withName("my-pod").withLabels(Map.of("a", "b"))
|
||||
.withAnnotations(Map.of("c", "d")).endMetadata().build();
|
||||
|
||||
WireMock.stubFor(WireMock.get("/api/v1/namespaces/a/pods/my-pod")
|
||||
.willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(pod))));
|
||||
|
||||
KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(true,
|
||||
"labels-prefix-", true, "annotations-prefix-", true, "ports-prefix", true, true);
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false, Set.of("a", "b"),
|
||||
true, 60L, false, "", Set.of(), Map.of(), "", metadata, 0, false, true);
|
||||
|
||||
KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(
|
||||
SHARED_INFORMER_FACTORY, serviceLister, endpointsLister, null, null, properties);
|
||||
discoveryClient.coreV1Api = new CoreV1Api(apiClient);
|
||||
|
||||
List<ServiceInstance> result = discoveryClient.getInstances("blue-service");
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
DefaultKubernetesServiceInstance serviceInstance = (DefaultKubernetesServiceInstance) result.get(0);
|
||||
Assertions.assertEquals(serviceInstance.getServiceId(), "blue-service");
|
||||
Assertions.assertEquals(serviceInstance.getHost(), "127.0.0.1");
|
||||
Assertions.assertEquals(serviceInstance.getPort(), 8080);
|
||||
Assertions.assertFalse(serviceInstance.isSecure());
|
||||
Assertions.assertEquals(serviceInstance.getUri().toASCIIString(), "http://127.0.0.1:8080");
|
||||
Assertions.assertEquals(serviceInstance.getMetadata(),
|
||||
Map.of("k8s_namespace", "a", "type", "ClusterIP", "ports-prefix<unset>", "8080"));
|
||||
Assertions.assertEquals(serviceInstance.podMetadata().get("labels"), Map.of("a", "b"));
|
||||
Assertions.assertEquals(serviceInstance.podMetadata().get("annotations"), Map.of("c", "d"));
|
||||
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
private Lister<V1Service> setupServiceLister(V1Service... services) {
|
||||
Cache<V1Service> serviceCache = new Cache<>();
|
||||
Lister<V1Service> serviceLister = new Lister<>(serviceCache);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.SharedInformerFactory;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
final class SharedInformerFactoryStub extends SharedInformerFactory {
|
||||
|
||||
@Override
|
||||
public void startAllRegisteredInformers() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.common.KubernetesObject;
|
||||
import io.kubernetes.client.informer.ResourceEventHandler;
|
||||
import io.kubernetes.client.informer.SharedInformer;
|
||||
import io.kubernetes.client.informer.TransformFunc;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
final class SharedInformerStub<T extends KubernetesObject> implements SharedInformer<T> {
|
||||
|
||||
@Override
|
||||
public void addEventHandler(ResourceEventHandler<T> handler) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEventHandlerWithResyncPeriod(ResourceEventHandler<T> handler, long resyncPeriod) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
|
||||
}
|
||||
|
||||
// this is the only method we care about
|
||||
@Override
|
||||
public boolean hasSynced() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lastSyncResourceVersion() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransform(TransformFunc transformFunc) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,6 +30,7 @@ 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.V1ServiceSpecBuilder;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -128,8 +129,8 @@ class KubernetesInformerReactiveDiscoveryClientTests {
|
||||
kubernetesDiscoveryProperties));
|
||||
|
||||
StepVerifier.create(discoveryClient.getInstances("test-svc-1"))
|
||||
.expectNext(new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("type", "ClusterIP", "<unset>", "8080", "k8s_namespace", "namespace1"), false,
|
||||
.expectNext(new DefaultKubernetesServiceInstance(null, "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("type", "ClusterIP", "port.<unset>", "8080", "k8s_namespace", "namespace1"), false,
|
||||
"namespace1", null))
|
||||
.expectComplete().verify();
|
||||
|
||||
@@ -149,8 +150,8 @@ class KubernetesInformerReactiveDiscoveryClientTests {
|
||||
kubernetesDiscoveryProperties));
|
||||
|
||||
StepVerifier.create(discoveryClient.getInstances("test-svc-1"))
|
||||
.expectNext(new DefaultKubernetesServiceInstance("", "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("type", "ClusterIP", "<unset>", "8080", "k8s_namespace", "namespace1"), false,
|
||||
.expectNext(new DefaultKubernetesServiceInstance(null, "test-svc-1", "2.2.2.2", 8080,
|
||||
Map.of("type", "ClusterIP", "port.<unset>", "8080", "k8s_namespace", "namespace1"), false,
|
||||
"namespace1", null))
|
||||
.expectComplete().verify();
|
||||
|
||||
@@ -237,9 +238,11 @@ class KubernetesInformerReactiveDiscoveryClientTests {
|
||||
boolean allNamespaces = true;
|
||||
|
||||
V1Service serviceXNamespaceA = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-a"));
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-a"))
|
||||
.spec(new V1ServiceSpecBuilder().withType("ClusterIP").build());
|
||||
V1Service serviceXNamespaceB = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-b"));
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-b"))
|
||||
.spec(new V1ServiceSpecBuilder().withType("ClusterIP").build());
|
||||
serviceCache.add(serviceXNamespaceA);
|
||||
serviceCache.add(serviceXNamespaceB);
|
||||
|
||||
@@ -286,9 +289,11 @@ class KubernetesInformerReactiveDiscoveryClientTests {
|
||||
boolean allNamespaces = true;
|
||||
|
||||
V1Service serviceXNamespaceA = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-a"));
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-a"))
|
||||
.spec(new V1ServiceSpecBuilder().withType("ClusterIP").build());
|
||||
V1Service serviceXNamespaceB = new V1Service()
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-b"));
|
||||
.metadata(new V1ObjectMeta().name("endpoints-x").namespace("namespace-b"))
|
||||
.spec(new V1ServiceSpecBuilder().withType("ClusterIP").build());
|
||||
serviceCache.add(serviceXNamespaceA);
|
||||
serviceCache.add(serviceXNamespaceB);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.util.ClientBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -52,13 +53,13 @@ import static org.mockito.Mockito.when;
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = KubernetesClientLoadBalancerPodModeTests.App.class)
|
||||
public class KubernetesClientLoadBalancerPodModeTests {
|
||||
class KubernetesClientLoadBalancerPodModeTests {
|
||||
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@Test
|
||||
public void testLoadBalancer() {
|
||||
void testLoadBalancer() {
|
||||
String resp = restTemplate.getForObject("http://servicea-wiremock", String.class);
|
||||
assertThat(resp).isEqualTo("hello");
|
||||
}
|
||||
@@ -67,12 +68,17 @@ public class KubernetesClientLoadBalancerPodModeTests {
|
||||
static class App {
|
||||
|
||||
@Bean
|
||||
public ApiClient apiClient() {
|
||||
CoreV1Api coreV1Api(ApiClient apiClient) {
|
||||
return new CoreV1Api(apiClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ApiClient apiClient() {
|
||||
return new ClientBuilder().build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BlockingLoadBalancerClient blockingLoadBalancerClient() {
|
||||
BlockingLoadBalancerClient blockingLoadBalancerClient() {
|
||||
BlockingLoadBalancerClient client = mock(BlockingLoadBalancerClient.class);
|
||||
try {
|
||||
ClientHttpResponse response = new MockClientHttpResponse("hello".getBytes(), HttpStatus.OK);
|
||||
@@ -87,14 +93,14 @@ public class KubernetesClientLoadBalancerPodModeTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KubernetesNamespaceProvider kubernetesNamespaceProvider() {
|
||||
KubernetesNamespaceProvider kubernetesNamespaceProvider() {
|
||||
KubernetesNamespaceProvider provider = mock(KubernetesNamespaceProvider.class);
|
||||
when(provider.getNamespace()).thenReturn("test");
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KubernetesInformerDiscoveryClient kubernetesInformerDiscoveryClient() {
|
||||
KubernetesInformerDiscoveryClient kubernetesInformerDiscoveryClient() {
|
||||
KubernetesInformerDiscoveryClient client = mock(KubernetesInformerDiscoveryClient.class);
|
||||
ServiceInstance instance = new DefaultServiceInstance("servicea-wiremock1", "servicea-wiremock", "fake",
|
||||
8888, false);
|
||||
|
||||
@@ -185,7 +185,10 @@ public final class DiscoveryClientUtils {
|
||||
if (!EXTERNAL_NAME.equals(serviceMetadata.get(SERVICE_TYPE))) {
|
||||
if (properties.metadata().addPodLabels() || properties.metadata().addPodAnnotations()) {
|
||||
|
||||
LOG.debug(() -> "Pod labels/annotations were requested");
|
||||
|
||||
if (podName != null) {
|
||||
LOG.debug(() -> "getting labels/annotation for pod: " + podName);
|
||||
PodLabelsAndAnnotations both = podLabelsAndMetadata.apply(podName);
|
||||
Map<String, Map<String, String>> result = new HashMap<>();
|
||||
if (properties.metadata().addPodLabels() && !both.labels().isEmpty()) {
|
||||
|
||||
@@ -375,7 +375,7 @@ class Fabric8KubernetesDiscoveryClientTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceWithoutPortsShouldBeSkipped() {
|
||||
void instanceWithoutSubsetsShouldBeSkipped() {
|
||||
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("endpoint1").withNamespace("test")
|
||||
.withLabels(Collections.emptyMap()).endMetadata().build();
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ class Fabric8KubernetesDiscoveryClientUtilsTests {
|
||||
new EndpointSubsetBuilder()
|
||||
.withPorts(new EndpointPortBuilder().withPort(8080).withName("https").build()).build());
|
||||
|
||||
Map<String, Integer> portsData = Fabric8KubernetesDiscoveryClientUtils.endpointSubsetsPortData(endpointSubsets);
|
||||
Map<String, Integer> portsData = endpointSubsetsPortData(endpointSubsets);
|
||||
Assertions.assertEquals(portsData.size(), 2);
|
||||
Assertions.assertEquals(portsData.get("https"), 8080);
|
||||
Assertions.assertEquals(portsData.get("<unset>"), 8081);
|
||||
@@ -306,7 +306,7 @@ class Fabric8KubernetesDiscoveryClientUtilsTests {
|
||||
new EndpointSubsetBuilder()
|
||||
.withPorts(new EndpointPortBuilder().withPort(8080).withName("https").build()).build());
|
||||
|
||||
Map<String, Integer> portsData = Fabric8KubernetesDiscoveryClientUtils.endpointSubsetsPortData(endpointSubsets);
|
||||
Map<String, Integer> portsData = endpointSubsetsPortData(endpointSubsets);
|
||||
Assertions.assertEquals(portsData.size(), 2);
|
||||
Assertions.assertEquals(portsData.get("https"), 8080);
|
||||
Assertions.assertEquals(portsData.get("http"), 8081);
|
||||
|
||||
@@ -26,15 +26,12 @@ import io.fabric8.kubernetes.api.model.EndpointSubset;
|
||||
import io.fabric8.kubernetes.api.model.EndpointSubsetBuilder;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class KubernetesDiscoveryClientUtilsTests {
|
||||
|
||||
/**
|
||||
|
||||
@@ -98,10 +98,20 @@ class KubernetesClientDiscoveryClientIT {
|
||||
*/
|
||||
@Test
|
||||
@Order(1)
|
||||
void testSimple() {
|
||||
void testSimple() throws Exception {
|
||||
|
||||
util.busybox(NAMESPACE, Phase.CREATE);
|
||||
|
||||
// find both pods
|
||||
String[] both = K3S.execInContainer("sh", "-c", "kubectl get pods -l app=busybox -o=name --no-headers")
|
||||
.getStdout().split("\n");
|
||||
// add a label to first pod
|
||||
K3S.execInContainer("sh", "-c",
|
||||
"kubectl label pods " + both[0].split("/")[1] + " custom-label=custom-label-value");
|
||||
// add annotation to the second pod
|
||||
K3S.execInContainer("sh", "-c",
|
||||
"kubectl annotate pods " + both[1].split("/")[1] + " custom-annotation=custom-annotation-value");
|
||||
|
||||
Commons.waitForLogStatement("serviceSharedInformer will use namespace : default", K3S, IMAGE_NAME);
|
||||
|
||||
WebClient servicesClient = builder().baseUrl("http://localhost/services").build();
|
||||
@@ -111,10 +121,11 @@ class KubernetesClientDiscoveryClientIT {
|
||||
|
||||
}).retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertEquals(servicesResult.size(), 3);
|
||||
Assertions.assertEquals(servicesResult.size(), 4);
|
||||
Assertions.assertTrue(servicesResult.contains("kubernetes"));
|
||||
Assertions.assertTrue(servicesResult.contains("spring-cloud-kubernetes-k8s-client-discovery"));
|
||||
Assertions.assertTrue(servicesResult.contains("busybox-service"));
|
||||
Assertions.assertTrue(servicesResult.contains("external-name-service"));
|
||||
|
||||
WebClient ourServiceClient = builder()
|
||||
.baseUrl("http://localhost/service-instances/spring-cloud-kubernetes-k8s-client-discovery").build();
|
||||
@@ -131,8 +142,8 @@ class KubernetesClientDiscoveryClientIT {
|
||||
Assertions.assertEquals(serviceInstance.getServiceId(), "spring-cloud-kubernetes-k8s-client-discovery");
|
||||
Assertions.assertNotNull(serviceInstance.getHost());
|
||||
Assertions.assertEquals(serviceInstance.getMetadata(),
|
||||
Map.of("app", "spring-cloud-kubernetes-k8s-client-discovery", "custom-spring-k8s", "spring-k8s", "http",
|
||||
"8080", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
Map.of("app", "spring-cloud-kubernetes-k8s-client-discovery", "custom-spring-k8s", "spring-k8s",
|
||||
"port.http", "8080", "k8s_namespace", "default", "type", "ClusterIP"));
|
||||
Assertions.assertEquals(serviceInstance.getPort(), 8080);
|
||||
Assertions.assertEquals(serviceInstance.getNamespace(), "default");
|
||||
|
||||
@@ -145,6 +156,26 @@ class KubernetesClientDiscoveryClientIT {
|
||||
|
||||
Assertions.assertEquals(busyBoxServiceInstances.size(), 2);
|
||||
|
||||
DefaultKubernetesServiceInstance withCustomLabel = busyBoxServiceInstances.stream()
|
||||
.filter(x -> x.podMetadata().getOrDefault("annotations", Map.of()).isEmpty()).toList().get(0);
|
||||
Assertions.assertEquals(withCustomLabel.getServiceId(), "busybox-service");
|
||||
Assertions.assertNotNull(withCustomLabel.getInstanceId());
|
||||
Assertions.assertNotNull(withCustomLabel.getHost());
|
||||
Assertions.assertEquals(withCustomLabel.getMetadata(),
|
||||
Map.of("k8s_namespace", "default", "type", "ClusterIP", "port.busybox-port", "80"));
|
||||
Assertions.assertTrue(withCustomLabel.podMetadata().get("labels").entrySet().stream()
|
||||
.anyMatch(x -> x.getKey().equals("custom-label") && x.getValue().equals("custom-label-value")));
|
||||
|
||||
DefaultKubernetesServiceInstance withCustomAnnotation = busyBoxServiceInstances.stream()
|
||||
.filter(x -> !x.podMetadata().getOrDefault("annotations", Map.of()).isEmpty()).toList().get(0);
|
||||
Assertions.assertEquals(withCustomAnnotation.getServiceId(), "busybox-service");
|
||||
Assertions.assertNotNull(withCustomAnnotation.getInstanceId());
|
||||
Assertions.assertNotNull(withCustomAnnotation.getHost());
|
||||
Assertions.assertEquals(withCustomAnnotation.getMetadata(),
|
||||
Map.of("k8s_namespace", "default", "type", "ClusterIP", "port.busybox-port", "80"));
|
||||
Assertions.assertTrue(withCustomAnnotation.podMetadata().get("annotations").entrySet().stream().anyMatch(
|
||||
x -> x.getKey().equals("custom-annotation") && x.getValue().equals("custom-annotation-value")));
|
||||
|
||||
// enforces this :
|
||||
// https://github.com/spring-cloud/spring-cloud-kubernetes/issues/1286
|
||||
WebClient clientForNonExistentService = builder().baseUrl("http://localhost/service-instances/non-existent")
|
||||
@@ -165,6 +196,8 @@ class KubernetesClientDiscoveryClientIT {
|
||||
* - config server is enabled for all namespaces
|
||||
* - wiremock service is deployed in namespace-a
|
||||
* - busybox service is deployed in namespace-b
|
||||
* - external-name-service is deployed in namespace "default" and such a service type is requested,
|
||||
* thus found also.
|
||||
*
|
||||
* Our discovery searches in all namespaces, thus finds them both.
|
||||
* </pre>
|
||||
@@ -187,11 +220,12 @@ class KubernetesClientDiscoveryClientIT {
|
||||
.bodyToMono(new ParameterizedTypeReference<List<String>>() {
|
||||
|
||||
}).retryWhen(retrySpec()).block();
|
||||
Assertions.assertEquals(servicesResult.size(), 7);
|
||||
Assertions.assertEquals(servicesResult.size(), 8);
|
||||
Assertions.assertTrue(servicesResult.contains("kubernetes"));
|
||||
Assertions.assertTrue(servicesResult.contains("spring-cloud-kubernetes-k8s-client-discovery"));
|
||||
Assertions.assertTrue(servicesResult.contains("busybox-service"));
|
||||
Assertions.assertTrue(servicesResult.contains("service-wiremock"));
|
||||
Assertions.assertTrue(servicesResult.contains("external-name-service"));
|
||||
|
||||
// enforces this :
|
||||
// https://github.com/spring-cloud/spring-cloud-kubernetes/issues/1286
|
||||
@@ -204,6 +238,23 @@ class KubernetesClientDiscoveryClientIT {
|
||||
|
||||
Assertions.assertEquals(resultForNonExistentService.size(), 0);
|
||||
|
||||
// test ExternalName fields
|
||||
WebClient externalNameClient = builder().baseUrl("http://localhost/service-instances/external-name-service")
|
||||
.build();
|
||||
List<DefaultKubernetesServiceInstance> externalNameServices = externalNameClient.method(HttpMethod.GET)
|
||||
.retrieve().bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
|
||||
|
||||
}).retryWhen(retrySpec()).block();
|
||||
DefaultKubernetesServiceInstance externalNameService = externalNameServices.get(0);
|
||||
Assertions.assertNotNull(externalNameService.getInstanceId());
|
||||
Assertions.assertEquals(externalNameService.getHost(), "spring.io");
|
||||
Assertions.assertEquals(externalNameService.getPort(), -1);
|
||||
Assertions.assertEquals(externalNameService.getMetadata(),
|
||||
Map.of("k8s_namespace", "default", "type", "ExternalName"));
|
||||
Assertions.assertFalse(externalNameService.isSecure());
|
||||
Assertions.assertEquals(externalNameService.getUri().toASCIIString(), "spring.io");
|
||||
Assertions.assertEquals(externalNameService.getScheme(), "http");
|
||||
|
||||
// do not remove wiremock in namespace a, it is required in the next test
|
||||
util.busybox(NAMESPACE_B, Phase.DELETE);
|
||||
util.deleteClusterWideClusterRoleBinding(NAMESPACE);
|
||||
@@ -352,10 +403,12 @@ class KubernetesClientDiscoveryClientIT {
|
||||
private static void manifests(Phase phase) {
|
||||
V1Deployment deployment = (V1Deployment) util.yaml("kubernetes-discovery-deployment.yaml");
|
||||
V1Service service = (V1Service) util.yaml("kubernetes-discovery-service.yaml");
|
||||
V1Service externalNameService = (V1Service) util.yaml("external-name-service.yaml");
|
||||
V1Ingress ingress = (V1Ingress) util.yaml("kubernetes-discovery-ingress.yaml");
|
||||
|
||||
if (phase.equals(Phase.DELETE)) {
|
||||
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
|
||||
util.deleteAndWait(NAMESPACE, null, externalNameService, null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -366,15 +419,27 @@ class KubernetesClientDiscoveryClientIT {
|
||||
.orElse(List.of()));
|
||||
V1EnvVar debugLevel = new V1EnvVar()
|
||||
.name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT_DISCOVERY").value("DEBUG");
|
||||
V1EnvVar commonsLevel = new V1EnvVar()
|
||||
.name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_COMMONS_DISCOVERY").value("DEBUG");
|
||||
|
||||
V1EnvVar debugLevelForClient = new V1EnvVar()
|
||||
.name("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_CLIENT").value("DEBUG");
|
||||
|
||||
V1EnvVar addLabels = new V1EnvVar().name("SPRING_CLOUD_KUBERNETES_DISCOVERY_METADATA_ADDPODLABELS")
|
||||
.value("TRUE");
|
||||
|
||||
V1EnvVar addAnnotations = new V1EnvVar()
|
||||
.name("SPRING_CLOUD_KUBERNETES_DISCOVERY_METADATA_ADDPODANNOTATIONS").value("TRUE");
|
||||
|
||||
envVars.add(debugLevel);
|
||||
envVars.add(debugLevelForClient);
|
||||
envVars.add(addLabels);
|
||||
envVars.add(addAnnotations);
|
||||
envVars.add(commonsLevel);
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
|
||||
|
||||
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
|
||||
util.createAndWait(NAMESPACE, null, null, externalNameService, null, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ final class KubernetesClientDiscoveryClientUtils {
|
||||
}
|
||||
""";
|
||||
|
||||
// patch to include all namespaces
|
||||
// patch to include all namespaces + external name services
|
||||
private static final String BODY_FIVE = """
|
||||
{
|
||||
"spec": {
|
||||
@@ -152,10 +152,16 @@ final class KubernetesClientDiscoveryClientUtils {
|
||||
"spec": {
|
||||
"containers": [{
|
||||
"name": "spring-cloud-kubernetes-k8s-client-discovery",
|
||||
"env": [{
|
||||
"name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_ALL_NAMESPACES",
|
||||
"value": "TRUE"
|
||||
}]
|
||||
"env": [
|
||||
{
|
||||
"name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_ALL_NAMESPACES",
|
||||
"value": "TRUE"
|
||||
},
|
||||
{
|
||||
"name": "SPRING_CLOUD_KUBERNETES_DISCOVERY_INCLUDEEXTERNALNAMESERVICES",
|
||||
"value": "TRUE"
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -373,6 +379,7 @@ final class KubernetesClientDiscoveryClientUtils {
|
||||
}
|
||||
|
||||
// add SPRING_CLOUD_KUBERNETES_DISCOVERY_ALL_NAMESPACES=TRUE
|
||||
// and SPRING_CLOUD_KUBERNETES_DISCOVERY_INCLUDEEXTERNALNAMESERVICES=TRUE
|
||||
static void patchForAllNamespaces(String deploymentName, String namespace) {
|
||||
patchWithMerge(deploymentName, namespace, BODY_FIVE, POD_LABELS);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ class KubernetesClientDiscoveryFilterITDelegate {
|
||||
Assertions.assertEquals(first.getPort(), 8080);
|
||||
Assertions.assertEquals(first.getNamespace(), "a-uat");
|
||||
Assertions.assertEquals(first.getMetadata(),
|
||||
Map.of("app", "service-wiremock", "http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP"));
|
||||
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP"));
|
||||
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ class KubernetesClientDiscoveryFilterITDelegate {
|
||||
Assertions.assertEquals(first.getPort(), 8080);
|
||||
Assertions.assertEquals(first.getNamespace(), "a-uat");
|
||||
Assertions.assertEquals(first.getMetadata(),
|
||||
Map.of("app", "service-wiremock", "http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP"));
|
||||
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP"));
|
||||
|
||||
DefaultKubernetesServiceInstance second = sorted.get(1);
|
||||
Assertions.assertEquals(second.getServiceId(), "service-wiremock");
|
||||
@@ -133,7 +133,7 @@ class KubernetesClientDiscoveryFilterITDelegate {
|
||||
Assertions.assertEquals(second.getPort(), 8080);
|
||||
Assertions.assertEquals(second.getNamespace(), "b-uat");
|
||||
Assertions.assertEquals(second.getMetadata(),
|
||||
Map.of("app", "service-wiremock", "http", "8080", "k8s_namespace", "b-uat", "type", "ClusterIP"));
|
||||
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "b-uat", "type", "ClusterIP"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@ import static org.awaitility.Awaitility.await;
|
||||
*/
|
||||
class KubernetesClientDiscoveryHealthITDelegate {
|
||||
|
||||
KubernetesClientDiscoveryHealthITDelegate() {
|
||||
|
||||
}
|
||||
|
||||
private static final String REACTIVE_STATUS = "$.components.reactiveDiscoveryClients.components.['Kubernetes Reactive Discovery Client'].status";
|
||||
|
||||
private static final String BLOCKING_STATUS = "$.components.discoveryComposite.components.discoveryClient.status";
|
||||
@@ -82,7 +86,8 @@ class KubernetesClientDiscoveryHealthITDelegate {
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
|
||||
.extractingJsonPathArrayValue(
|
||||
"$.components.discoveryComposite.components.discoveryClient.details.services")
|
||||
.containsExactlyInAnyOrder("spring-cloud-kubernetes-k8s-client-discovery", "kubernetes");
|
||||
.containsExactlyInAnyOrder("spring-cloud-kubernetes-k8s-client-discovery", "kubernetes",
|
||||
"external-name-service");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(REACTIVE_STATUS);
|
||||
|
||||
@@ -119,7 +124,8 @@ class KubernetesClientDiscoveryHealthITDelegate {
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue(
|
||||
"$.components.reactiveDiscoveryClients.components.['Kubernetes Reactive Discovery Client'].details.services")
|
||||
.containsExactlyInAnyOrder("spring-cloud-kubernetes-k8s-client-discovery", "kubernetes");
|
||||
.containsExactlyInAnyOrder("spring-cloud-kubernetes-k8s-client-discovery", "kubernetes",
|
||||
"external-name-service");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).doesNotHaveJsonPath(BLOCKING_STATUS);
|
||||
|
||||
@@ -167,7 +173,8 @@ class KubernetesClientDiscoveryHealthITDelegate {
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
|
||||
.extractingJsonPathArrayValue(
|
||||
"$.components.discoveryComposite.components.discoveryClient.details.services")
|
||||
.containsExactlyInAnyOrder("spring-cloud-kubernetes-k8s-client-discovery", "kubernetes");
|
||||
.containsExactlyInAnyOrder("spring-cloud-kubernetes-k8s-client-discovery", "kubernetes",
|
||||
"external-name-service");
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult))
|
||||
.extractingJsonPathStringValue("$.components.reactiveDiscoveryClients.status").isEqualTo("UP");
|
||||
@@ -178,7 +185,8 @@ class KubernetesClientDiscoveryHealthITDelegate {
|
||||
|
||||
Assertions.assertThat(BASIC_JSON_TESTER.from(healthResult)).extractingJsonPathArrayValue(
|
||||
"$.components.reactiveDiscoveryClients.components.['Kubernetes Reactive Discovery Client'].details.services")
|
||||
.containsExactlyInAnyOrder("spring-cloud-kubernetes-k8s-client-discovery", "kubernetes");
|
||||
.containsExactlyInAnyOrder("spring-cloud-kubernetes-k8s-client-discovery", "kubernetes",
|
||||
"external-name-service");
|
||||
|
||||
// assert health/info also
|
||||
assertHealth(healthResult);
|
||||
|
||||
@@ -50,9 +50,10 @@ class KubernetesClientDiscoveryPodMetadataITDelegate {
|
||||
|
||||
}).retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertEquals(servicesResult.size(), 2);
|
||||
Assertions.assertEquals(servicesResult.size(), 3);
|
||||
Assertions.assertTrue(servicesResult.contains("kubernetes"));
|
||||
Assertions.assertTrue(servicesResult.contains("spring-cloud-kubernetes-k8s-client-discovery"));
|
||||
Assertions.assertTrue(servicesResult.contains("external-name-service"));
|
||||
|
||||
WebClient ourServiceClient = builder()
|
||||
.baseUrl("http://localhost//service-instances/spring-cloud-kubernetes-k8s-client-discovery").build();
|
||||
@@ -69,7 +70,7 @@ class KubernetesClientDiscoveryPodMetadataITDelegate {
|
||||
Assertions.assertEquals(serviceInstance.getServiceId(), "spring-cloud-kubernetes-k8s-client-discovery");
|
||||
Assertions.assertNotNull(serviceInstance.getHost());
|
||||
Assertions.assertEquals(serviceInstance.getMetadata(),
|
||||
Map.of("http", "8080", "k8s_namespace", "default", "type", "ClusterIP", "label-app",
|
||||
Map.of("port.http", "8080", "k8s_namespace", "default", "type", "ClusterIP", "label-app",
|
||||
"spring-cloud-kubernetes-k8s-client-discovery", "annotation-custom-spring-k8s", "spring-k8s"));
|
||||
Assertions.assertEquals(serviceInstance.getPort(), 8080);
|
||||
Assertions.assertEquals(serviceInstance.getNamespace(), "default");
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: external-name-service
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: spring.io
|
||||
@@ -116,20 +116,25 @@ public final class Util {
|
||||
@Nullable V1Ingress ingress, boolean changeVersion) {
|
||||
try {
|
||||
|
||||
String imageFromDeployment = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage();
|
||||
if (changeVersion) {
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0)
|
||||
.setImage(imageFromDeployment + ":" + pomVersion());
|
||||
}
|
||||
else {
|
||||
String[] image = imageFromDeployment.split(":", 2);
|
||||
pullImage(image[0], image[1], container);
|
||||
loadImage(image[0], image[1], name, container);
|
||||
coreV1Api.createNamespacedService(namespace, service, null, null, null, null);
|
||||
|
||||
if (deployment != null) {
|
||||
String imageFromDeployment = deployment.getSpec().getTemplate().getSpec().getContainers().get(0)
|
||||
.getImage();
|
||||
if (changeVersion) {
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0)
|
||||
.setImage(imageFromDeployment + ":" + pomVersion());
|
||||
}
|
||||
else {
|
||||
String[] image = imageFromDeployment.split(":", 2);
|
||||
pullImage(image[0], image[1], container);
|
||||
loadImage(image[0], image[1], name, container);
|
||||
}
|
||||
|
||||
appsV1Api.createNamespacedDeployment(namespace, deployment, null, null, null, null);
|
||||
waitForDeployment(namespace, deployment);
|
||||
}
|
||||
|
||||
appsV1Api.createNamespacedDeployment(namespace, deployment, null, null, null, null);
|
||||
coreV1Api.createNamespacedService(namespace, service, null, null, null, null);
|
||||
waitForDeployment(namespace, deployment);
|
||||
if (ingress != null) {
|
||||
networkingV1Api.createNamespacedIngress(namespace, ingress, null, null, null, null);
|
||||
waitForIngress(namespace, ingress);
|
||||
@@ -193,20 +198,27 @@ public final class Util {
|
||||
|
||||
public void deleteAndWait(String namespace, V1Deployment deployment, V1Service service,
|
||||
@Nullable V1Ingress ingress) {
|
||||
String deploymentName = deploymentName(deployment);
|
||||
|
||||
if (deployment != null) {
|
||||
try {
|
||||
String deploymentName = deploymentName(deployment);
|
||||
Map<String, String> podLabels = appsV1Api.readNamespacedDeployment(deploymentName, namespace, null)
|
||||
.getSpec().getTemplate().getMetadata().getLabels();
|
||||
appsV1Api.deleteNamespacedDeployment(deploymentName, namespace, null, null, null, null, null, null);
|
||||
coreV1Api.deleteCollectionNamespacedPod(namespace, null, null, null, null, null,
|
||||
labelSelector(podLabels), null, null, null, null, null, null, null);
|
||||
waitForDeploymentToBeDeleted(deploymentName, namespace);
|
||||
waitForDeploymentPodsToBeDeleted(podLabels, namespace);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
String serviceName = serviceName(service);
|
||||
try {
|
||||
|
||||
Map<String, String> podLabels = appsV1Api.readNamespacedDeployment(deploymentName, namespace, null)
|
||||
.getSpec().getTemplate().getMetadata().getLabels();
|
||||
|
||||
appsV1Api.deleteNamespacedDeployment(deploymentName, namespace, null, null, null, null, null, null);
|
||||
coreV1Api.deleteNamespacedService(serviceName, namespace, null, null, null, null, null, null);
|
||||
coreV1Api.deleteCollectionNamespacedPod(namespace, null, null, null, null, null, labelSelector(podLabels),
|
||||
null, null, null, null, null, null, null);
|
||||
waitForDeploymentToBeDeleted(deploymentName, namespace);
|
||||
waitForDeploymentPodsToBeDeleted(podLabels, namespace);
|
||||
|
||||
if (ingress != null) {
|
||||
String ingressName = ingressName(ingress);
|
||||
networkingV1Api.deleteNamespacedIngress(ingressName, namespace, null, null, null, null, null, null);
|
||||
|
||||
Reference in New Issue
Block a user