Skip to content

Commit

Permalink
Complete alignment between fabric8 and k8s discovery clients (#1500)
Browse files Browse the repository at this point in the history
  • Loading branch information
wind57 authored Nov 14, 2023
1 parent 901089d commit ecfb805
Show file tree
Hide file tree
Showing 28 changed files with 1,903 additions and 483 deletions.
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,35 @@
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;
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
Expand Down Expand Up @@ -82,40 +87,6 @@ static boolean matchesServiceLabels(V1Service service, KubernetesDiscoveryProper

}

/**
* 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;
Expand Down Expand Up @@ -159,4 +130,38 @@ static void postConstruct(List<SharedInformerFactory> sharedInformerFactories,

}

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;
}

}
Loading

0 comments on commit ecfb805

Please sign in to comment.