diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/DiscoveryClientUtils.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/DiscoveryClientUtils.java index 3b2bd0e8..e3bbed35 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/DiscoveryClientUtils.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/DiscoveryClientUtils.java @@ -20,14 +20,19 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; +import jakarta.annotation.Nullable; import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.client.ServiceInstance; import org.springframework.core.log.LogAccessor; import org.springframework.util.StringUtils; import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.keysWithPrefix; +import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.EXTERNAL_NAME; 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.NAMESPACE_METADATA_KEY; @@ -130,6 +135,32 @@ public final class DiscoveryClientUtils { } } + public static ServiceInstance serviceInstance(@Nullable ServicePortSecureResolver servicePortSecureResolver, + ServiceMetadataForServiceInstance serviceMetadataForServiceInstance, + Supplier instanceIdAndHost, + Function podLabelsAndMetadata, ServicePortNameAndNumber portData, + String serviceId, Map serviceMetadata, String namespace, + KubernetesDiscoveryProperties properties) { + + InstanceIdHostPodName data = instanceIdAndHost.get(); + + boolean secured; + if (servicePortSecureResolver == null) { + secured = false; + } + else { + secured = servicePortSecureResolver.resolve(new ServicePortSecureResolver.Input(portData, + serviceMetadataForServiceInstance.name(), serviceMetadataForServiceInstance.labels(), + serviceMetadataForServiceInstance.annotations())); + } + + Map> podMetadata = podMetadata(data.podName(), serviceMetadata, properties, + podLabelsAndMetadata); + + return new DefaultKubernetesServiceInstance(data.instanceId(), serviceId, data.host(), portData.portNumber(), + serviceMetadata, secured, namespace, null, podMetadata); + } + /** * take primary-port-name from service label "PRIMARY_PORT_NAME_LABEL_KEY" if it * exists, otherwise from KubernetesDiscoveryProperties if it exists, otherwise null. @@ -154,6 +185,32 @@ public final class DiscoveryClientUtils { return primaryPortName; } + static Map> podMetadata(String podName, Map serviceMetadata, + KubernetesDiscoveryProperties properties, Function podLabelsAndMetadata) { + if (!EXTERNAL_NAME.equals(serviceMetadata.get(SERVICE_TYPE))) { + if (properties.metadata().addPodLabels() || properties.metadata().addPodAnnotations()) { + + if (podName != null) { + PodLabelsAndAnnotations both = podLabelsAndMetadata.apply(podName); + Map> result = new HashMap<>(); + if (properties.metadata().addPodLabels() && !both.labels().isEmpty()) { + result.put("labels", both.labels()); + } + + if (properties.metadata().addPodAnnotations() && !both.annotations().isEmpty()) { + result.put("annotations", both.annotations()); + } + + LOG.debug(() -> "adding podMetadata : " + result + " from pod : " + podName); + return result; + } + + } + } + + return Map.of(); + } + private static Optional fromMap(Map existingPorts, String key, String message) { Integer fromPrimaryPortName = existingPorts.get(key); diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/InstanceIdHostPodName.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/InstanceIdHostPodName.java new file mode 100644 index 00000000..f5c321ad --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/InstanceIdHostPodName.java @@ -0,0 +1,24 @@ +/* + * 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.commons.discovery; + +/** + * computes instanceId, host and podName. All needed when calculating ServiceInstance. + * @author wind57 + */ +public record InstanceIdHostPodName(String instanceId, String host, String podName) { +} diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/PodLabelsAndAnnotations.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/PodLabelsAndAnnotations.java new file mode 100644 index 00000000..4949f618 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/PodLabelsAndAnnotations.java @@ -0,0 +1,26 @@ +/* + * 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.commons.discovery; + +import java.util.Map; + +/** + * Holds pod labels and annotations. + * @author wind57 + */ +public record PodLabelsAndAnnotations(Map labels, Map annotations) { +} diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/ServiceMetadataForServiceInstance.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/ServiceMetadataForServiceInstance.java new file mode 100644 index 00000000..600db6c8 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/ServiceMetadataForServiceInstance.java @@ -0,0 +1,29 @@ +/* + * 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.commons.discovery; + +import java.util.Map; + +/** + * Holds service name, labels and annotations. + * + * @author wind57 + * + */ +public record ServiceMetadataForServiceInstance(String name, Map labels, + Map annotations) { +} diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/discovery/DiscoveryClientUtilsTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/discovery/DiscoveryClientUtilsTests.java index d25d576a..c58d450a 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/discovery/DiscoveryClientUtilsTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/discovery/DiscoveryClientUtilsTests.java @@ -19,6 +19,7 @@ package org.springframework.cloud.kubernetes.commons.discovery; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import org.junit.jupiter.api.Assertions; @@ -27,6 +28,7 @@ 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.client.ServiceInstance; import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.PRIMARY_PORT_NAME_LABEL_KEY; @@ -626,6 +628,290 @@ class DiscoveryClientUtilsTests { Assertions.assertTrue(output.getOut().contains("found primary-port-name via 'http' to match port : 8082")); } + @Test + void testServiceInstance() { + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + false, "", Set.of(), Map.of(), "", KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false, false); + ServicePortSecureResolver resolver = new ServicePortSecureResolver(properties); + + ServicePortNameAndNumber portData = new ServicePortNameAndNumber(8080, "http"); + ServiceMetadataForServiceInstance forServiceInstance = new ServiceMetadataForServiceInstance("my-service", + Map.of(), Map.of()); + InstanceIdHostPodName instanceIdHostPodName = new InstanceIdHostPodName("123", "127.0.0.1", null); + Map serviceMetadata = Map.of("a", "b"); + + ServiceInstance serviceInstance = DiscoveryClientUtils.serviceInstance(resolver, forServiceInstance, + () -> instanceIdHostPodName, null, portData, "my-service", serviceMetadata, "k8s", properties); + Assertions.assertTrue(serviceInstance instanceof DefaultKubernetesServiceInstance); + DefaultKubernetesServiceInstance defaultInstance = (DefaultKubernetesServiceInstance) serviceInstance; + Assertions.assertEquals(defaultInstance.getInstanceId(), "123"); + Assertions.assertEquals(defaultInstance.getServiceId(), "my-service"); + Assertions.assertEquals(defaultInstance.getHost(), "127.0.0.1"); + Assertions.assertEquals(defaultInstance.getPort(), 8080); + Assertions.assertFalse(defaultInstance.isSecure()); + Assertions.assertEquals(defaultInstance.getUri().toASCIIString(), "http://127.0.0.1:8080"); + Assertions.assertEquals(defaultInstance.getMetadata(), Map.of("a", "b")); + Assertions.assertEquals(defaultInstance.getScheme(), "http"); + Assertions.assertEquals(defaultInstance.getNamespace(), "k8s"); + Assertions.assertNull(defaultInstance.getCluster()); + } + + @Test + void testExternalNameServiceInstance() { + + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + false, "", Set.of(), Map.of(), "", KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false, false); + + ServicePortNameAndNumber portData = new ServicePortNameAndNumber(-1, "http"); + ServiceMetadataForServiceInstance forServiceInstance = new ServiceMetadataForServiceInstance("my-service", + Map.of(), Map.of()); + InstanceIdHostPodName instanceIdHostPodName = new InstanceIdHostPodName("123", "spring.io", null); + Map serviceMetadata = Map.of("a", "b"); + + ServiceInstance serviceInstance = DiscoveryClientUtils.serviceInstance(null, forServiceInstance, + () -> instanceIdHostPodName, null, portData, "my-service", serviceMetadata, "k8s", properties); + + Assertions.assertTrue(serviceInstance instanceof DefaultKubernetesServiceInstance); + DefaultKubernetesServiceInstance defaultInstance = (DefaultKubernetesServiceInstance) serviceInstance; + Assertions.assertEquals(defaultInstance.getInstanceId(), "123"); + Assertions.assertEquals(defaultInstance.getServiceId(), "my-service"); + Assertions.assertEquals(defaultInstance.getHost(), "spring.io"); + Assertions.assertEquals(defaultInstance.getPort(), -1); + Assertions.assertFalse(defaultInstance.isSecure()); + Assertions.assertEquals(defaultInstance.getUri().toASCIIString(), "spring.io"); + Assertions.assertEquals(defaultInstance.getMetadata(), Map.of("a", "b")); + Assertions.assertEquals(defaultInstance.getScheme(), "http"); + Assertions.assertEquals(defaultInstance.getNamespace(), "k8s"); + Assertions.assertNull(defaultInstance.getCluster()); + } + + /** + * type is ExternalName, as such we do nothing. + */ + @Test + void testPodMetadataExternalName() { + boolean addLabels = false; + boolean addAnnotations = false; + String podName = "pod-name"; + Map serviceMetadata = Map.of("type", "ExternalName"); + KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, + "", false, "", addLabels, addAnnotations); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + false, "", Set.of(), Map.of(), "", metadata, 0, false, false); + Function podLabelsAndMetadata = x -> null; + + Map> result = DiscoveryClientUtils.podMetadata(podName, serviceMetadata, properties, + podLabelsAndMetadata); + Assertions.assertTrue(result.isEmpty()); + } + + /** + * type is not ExternalName, but labels and annotations have not been requested. As + * such, we do nothing. + */ + @Test + void testPodMetadataNotExternalNameLabelsNorAnnotationsIncluded() { + boolean addLabels = false; + boolean addAnnotations = false; + String podName = "pod-name"; + Map serviceMetadata = Map.of("type", "ClusterIP"); + KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, + "", false, "", addLabels, addAnnotations); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + false, "", Set.of(), Map.of(), "", metadata, 0, false, false); + Function podLabelsAndMetadata = x -> null; + + Map> result = DiscoveryClientUtils.podMetadata(podName, serviceMetadata, properties, + podLabelsAndMetadata); + Assertions.assertTrue(result.isEmpty()); + } + + /** + *
+	 *     - type is not ExternalName
+	 *     - labels and annotations have been requested
+	 *     - podName is null
+	 *
+	 *     As such we do nothing.
+	 * 
+ */ + @Test + void testPodMetadataNotExternalNameLabelsAndAnnotationsIncludedPodNameNull() { + boolean addLabels = true; + boolean addAnnotations = true; + String podName = null; + Map serviceMetadata = Map.of("type", "ClusterIP"); + KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, + "", false, "", addLabels, addAnnotations); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + false, "", Set.of(), Map.of(), "", metadata, 0, false, false); + Function podLabelsAndMetadata = x -> null; + + Map> result = DiscoveryClientUtils.podMetadata(podName, serviceMetadata, properties, + podLabelsAndMetadata); + Assertions.assertTrue(result.isEmpty()); + } + + /** + *
+	 *     - type is not ExternalName
+	 *     - labels have been requested
+	 *     - labels are empty
+	 *     - podName is not null.
+	 *
+	 *     As such we add empty labels to pod metadata.
+	 * 
+ */ + @Test + void testPodMetadataOnlyLabelsRequestedButAreEmpty(CapturedOutput output) { + boolean addLabels = true; + boolean addAnnotations = false; + String podName = "my-pod"; + Map serviceMetadata = Map.of("type", "ClusterIP"); + KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, + "", false, "", addLabels, addAnnotations); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + false, "", Set.of(), Map.of(), "", metadata, 0, false, false); + + PodLabelsAndAnnotations both = new PodLabelsAndAnnotations(Map.of(), Map.of("c", "d")); + Function podLabelsAndMetadata = x -> both; + + Map> result = DiscoveryClientUtils.podMetadata(podName, serviceMetadata, properties, + podLabelsAndMetadata); + Assertions.assertTrue(result.isEmpty()); + + Assertions.assertTrue(output.getOut().contains("adding podMetadata : {} from pod : my-pod")); + } + + /** + *
+	 *     - type is not ExternalName
+	 *     - labels have been requested
+	 *     - labels are not empty
+	 *     - podName is not null.
+	 *
+	 *     As such we add non empty labels to pod metadata.
+	 * 
+ */ + @Test + void testPodMetadataOnlyLabelsRequestedAndAreNotEmpty(CapturedOutput output) { + boolean addLabels = true; + boolean addAnnotations = false; + String podName = "my-pod"; + Map serviceMetadata = Map.of("type", "ClusterIP"); + KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, + "", false, "", addLabels, addAnnotations); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + false, "", Set.of(), Map.of(), "", metadata, 0, false, false); + + PodLabelsAndAnnotations both = new PodLabelsAndAnnotations(Map.of("a", "b"), Map.of("c", "d")); + Function podLabelsAndMetadata = x -> both; + + Map> result = DiscoveryClientUtils.podMetadata(podName, serviceMetadata, properties, + podLabelsAndMetadata); + Assertions.assertEquals(result.size(), 1); + Assertions.assertEquals(result.get("labels"), Map.of("a", "b")); + + Assertions.assertTrue(output.getOut().contains("adding podMetadata : {labels={a=b}} from pod : my-pod")); + } + + /** + *
+	 *     - type is not ExternalName
+	 *     - annotation have been requested
+	 *     - annotation are empty
+	 *     - podName is not null.
+	 *
+	 *     As such we add empty labels to pod metadata.
+	 * 
+ */ + @Test + void testPodMetadataOnlyAnnotationsRequestedButAreEmpty(CapturedOutput output) { + boolean addLabels = false; + boolean addAnnotations = true; + String podName = "my-pod"; + Map serviceMetadata = Map.of("type", "ClusterIP"); + KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, + "", false, "", addLabels, addAnnotations); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + false, "", Set.of(), Map.of(), "", metadata, 0, false, false); + + PodLabelsAndAnnotations both = new PodLabelsAndAnnotations(Map.of("a", "b"), Map.of()); + Function podLabelsAndMetadata = x -> both; + + Map> result = DiscoveryClientUtils.podMetadata(podName, serviceMetadata, properties, + podLabelsAndMetadata); + Assertions.assertTrue(result.isEmpty()); + + Assertions.assertTrue(output.getOut().contains("adding podMetadata : {} from pod : my-pod")); + } + + /** + *
+	 *     - type is not ExternalName
+	 *     - annotations have been requested
+	 *     - annotation are not empty
+	 *     - podName is not null.
+	 *
+	 *     As such we add non empty labels to pod metadata.
+	 * 
+ */ + @Test + void testPodMetadataOnlyAnnotationsRequestedAndAreNotEmpty(CapturedOutput output) { + boolean addLabels = false; + boolean addAnnotations = true; + String podName = "my-pod"; + Map serviceMetadata = Map.of("type", "ClusterIP"); + KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, + "", false, "", addLabels, addAnnotations); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + false, "", Set.of(), Map.of(), "", metadata, 0, false, false); + + PodLabelsAndAnnotations both = new PodLabelsAndAnnotations(Map.of("a", "b"), Map.of("c", "d")); + Function podLabelsAndMetadata = x -> both; + + Map> result = DiscoveryClientUtils.podMetadata(podName, serviceMetadata, properties, + podLabelsAndMetadata); + Assertions.assertEquals(result.size(), 1); + Assertions.assertEquals(result.get("annotations"), Map.of("c", "d")); + + Assertions.assertTrue(output.getOut().contains("adding podMetadata : {annotations={c=d}} from pod : my-pod")); + } + + /** + *
+	 *     - type is not ExternalName
+	 *     - annotations have been requested
+	 *     - annotation are not empty
+	 *     - podName is not null.
+	 *
+	 *     As such we add non empty labels to pod metadata.
+	 * 
+ */ + @Test + void testPodMetadataBothLabelsAndAnnotations(CapturedOutput output) { + boolean addLabels = true; + boolean addAnnotations = true; + String podName = "my-pod"; + Map serviceMetadata = Map.of("type", "ClusterIP"); + KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, + "", false, "", addLabels, addAnnotations); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + false, "", Set.of(), Map.of(), "", metadata, 0, false, false); + + PodLabelsAndAnnotations both = new PodLabelsAndAnnotations(Map.of("a", "b"), Map.of("c", "d")); + Function podLabelsAndMetadata = x -> both; + + Map> result = DiscoveryClientUtils.podMetadata(podName, serviceMetadata, properties, + podLabelsAndMetadata); + Assertions.assertEquals(result.size(), 2); + Assertions.assertEquals(result.get("annotations"), Map.of("c", "d")); + Assertions.assertEquals(result.get("labels"), Map.of("a", "b")); + + Assertions.assertTrue( + output.getOut().contains("adding podMetadata : {annotations={c=d}, labels={a=b}} from pod : my-pod")); + } + private String filterOnK8sNamespaceAndType(Map result) { return result.entrySet().stream().filter(en -> !en.getKey().contains("k8s_namespace")) .filter(en -> !en.getKey().equals("type")) diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8InstanceIdHostPodNameSupplier.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8InstanceIdHostPodNameSupplier.java new file mode 100644 index 00000000..df777ed9 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8InstanceIdHostPodNameSupplier.java @@ -0,0 +1,66 @@ +/* + * 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.fabric8.discovery; + +import java.util.Optional; +import java.util.function.Supplier; + +import io.fabric8.kubernetes.api.model.EndpointAddress; +import io.fabric8.kubernetes.api.model.ObjectReference; +import io.fabric8.kubernetes.api.model.Service; + +import org.springframework.cloud.kubernetes.commons.discovery.InstanceIdHostPodName; + +/** + * computes instanceId, host and podName. All needed when calculating ServiceInstance. + * + * @author wind57 + */ +final class Fabric8InstanceIdHostPodNameSupplier implements Supplier { + + private final EndpointAddress endpointAddress; + + private final Service service; + + Fabric8InstanceIdHostPodNameSupplier(EndpointAddress endpointAddress, Service service) { + this.endpointAddress = endpointAddress; + this.service = service; + } + + @Override + public InstanceIdHostPodName get() { + return new InstanceIdHostPodName(instanceId(), host(), podName()); + } + + // instanceId is usually the pod-uid as seen in the .metadata.uid + private String instanceId() { + return Optional.ofNullable(endpointAddress).map(EndpointAddress::getTargetRef).map(ObjectReference::getUid) + .orElseGet(() -> service.getMetadata().getUid()); + } + + private String host() { + return Optional.ofNullable(endpointAddress).map(EndpointAddress::getIp) + .orElseGet(() -> service.getSpec().getExternalName()); + } + + private String podName() { + return Optional.ofNullable(endpointAddress).map(EndpointAddress::getTargetRef) + .filter(objectReference -> "Pod".equals(objectReference.getKind())).map(ObjectReference::getName) + .orElse(null); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesDiscoveryClientUtils.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesDiscoveryClientUtils.java index db2cb8dc..28d62495 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesDiscoveryClientUtils.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8KubernetesDiscoveryClientUtils.java @@ -17,7 +17,6 @@ package org.springframework.cloud.kubernetes.fabric8.discovery; import java.util.ArrayList; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -31,9 +30,6 @@ import io.fabric8.kubernetes.api.model.EndpointPort; import io.fabric8.kubernetes.api.model.EndpointSubset; import io.fabric8.kubernetes.api.model.Endpoints; import io.fabric8.kubernetes.api.model.EndpointsList; -import io.fabric8.kubernetes.api.model.ObjectMeta; -import io.fabric8.kubernetes.api.model.ObjectReference; -import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceList; import io.fabric8.kubernetes.client.KubernetesClient; @@ -44,21 +40,13 @@ import io.fabric8.kubernetes.client.dsl.ServiceResource; import jakarta.annotation.Nullable; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; -import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance; import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; -import org.springframework.cloud.kubernetes.commons.discovery.ServicePortNameAndNumber; -import org.springframework.cloud.kubernetes.commons.discovery.ServicePortSecureResolver; import org.springframework.cloud.kubernetes.fabric8.Fabric8Utils; import org.springframework.core.log.LogAccessor; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.EXTERNAL_NAME; -import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.PRIMARY_PORT_NAME_LABEL_KEY; -import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.SERVICE_TYPE; - /** * @author wind57 */ @@ -77,30 +65,6 @@ final class Fabric8KubernetesDiscoveryClientUtils { return new EndpointSubsetNS(endpoints.getMetadata().getNamespace(), endpoints.getSubsets()); } - /** - * take primary-port-name from service label "PRIMARY_PORT_NAME_LABEL_KEY" if it - * exists, otherwise from KubernetesDiscoveryProperties if it exists, otherwise null. - */ - static String primaryPortName(KubernetesDiscoveryProperties properties, Service service, String serviceId) { - String primaryPortNameFromProperties = properties.primaryPortName(); - Map serviceLabels = service.getMetadata().getLabels(); - - // the value from labels takes precedence over the one from properties - String primaryPortName = Optional - .ofNullable(Optional.ofNullable(serviceLabels).orElse(Map.of()).get(PRIMARY_PORT_NAME_LABEL_KEY)) - .orElse(primaryPortNameFromProperties); - - if (primaryPortName == null) { - LOG.debug( - () -> "did not find a primary-port-name in neither properties nor service labels for service with ID : " - + serviceId); - return null; - } - - LOG.debug(() -> "will use primaryPortName : " + primaryPortName + " for service with ID = " + serviceId); - return primaryPortName; - } - static List endpoints(KubernetesDiscoveryProperties properties, KubernetesClient client, KubernetesNamespaceProvider namespaceProvider, String target, @Nullable String serviceName, Predicate filter) { @@ -195,34 +159,6 @@ final class Fabric8KubernetesDiscoveryClientUtils { return addresses; } - static ServiceInstance serviceInstance(@Nullable ServicePortSecureResolver servicePortSecureResolver, - Service service, @Nullable EndpointAddress endpointAddress, ServicePortNameAndNumber portData, - String serviceId, Map serviceMetadata, String namespace, - KubernetesDiscoveryProperties properties, KubernetesClient client) { - // instanceId is usually the pod-uid as seen in the .metadata.uid - String instanceId = Optional.ofNullable(endpointAddress).map(EndpointAddress::getTargetRef) - .map(ObjectReference::getUid).orElseGet(() -> service.getMetadata().getUid()); - - boolean secured; - if (servicePortSecureResolver == null) { - secured = false; - } - else { - secured = servicePortSecureResolver - .resolve(new ServicePortSecureResolver.Input(portData, service.getMetadata().getName(), - service.getMetadata().getLabels(), service.getMetadata().getAnnotations())); - } - - String host = Optional.ofNullable(endpointAddress).map(EndpointAddress::getIp) - .orElseGet(() -> service.getSpec().getExternalName()); - - Map> podMetadata = podMetadata(client, serviceMetadata, properties, endpointAddress, - namespace); - - return new DefaultKubernetesServiceInstance(instanceId, serviceId, host, portData.portNumber(), serviceMetadata, - secured, namespace, null, podMetadata); - } - static List services(KubernetesDiscoveryProperties properties, KubernetesClient client, KubernetesNamespaceProvider namespaceProvider, Predicate predicate, Map fieldFilters, String target) { @@ -252,37 +188,6 @@ final class Fabric8KubernetesDiscoveryClientUtils { return services; } - static Map> podMetadata(KubernetesClient client, Map serviceMetadata, - KubernetesDiscoveryProperties properties, EndpointAddress endpointAddress, String namespace) { - if (!EXTERNAL_NAME.equals(serviceMetadata.get(SERVICE_TYPE))) { - if (properties.metadata().addPodLabels() || properties.metadata().addPodAnnotations()) { - String podName = Optional.ofNullable(endpointAddress).map(EndpointAddress::getTargetRef) - .filter(objectReference -> "Pod".equals(objectReference.getKind())) - .map(ObjectReference::getName).orElse(null); - - if (podName != null) { - ObjectMeta metadata = Optional - .ofNullable(client.pods().inNamespace(namespace).withName(podName).get()) - .map(Pod::getMetadata).orElse(new ObjectMeta()); - Map> result = new HashMap<>(); - if (properties.metadata().addPodLabels() && !metadata.getLabels().isEmpty()) { - result.put("labels", metadata.getLabels()); - } - - if (properties.metadata().addPodAnnotations() && !metadata.getAnnotations().isEmpty()) { - result.put("annotations", metadata.getAnnotations()); - } - - LOG.debug(() -> "adding podMetadata : " + result + " from pod : " + podName); - return result; - } - - } - } - - return Map.of(); - } - static Map portsData(List endpointSubsets) { return endpointSubsets.stream().flatMap(endpointSubset -> endpointSubset.getPorts().stream()) .filter(port -> StringUtils.hasText(port.getName())) diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8PodLabelsAndAnnotationsSupplier.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8PodLabelsAndAnnotationsSupplier.java new file mode 100644 index 00000000..46b1526f --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8PodLabelsAndAnnotationsSupplier.java @@ -0,0 +1,51 @@ +/* + * 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.fabric8.discovery; + +import java.util.Optional; +import java.util.function.Function; + +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.client.KubernetesClient; + +import org.springframework.cloud.kubernetes.commons.discovery.PodLabelsAndAnnotations; + +/** + * A way to get labels and annotations from a podName. + * + * @author wind57 + */ +final class Fabric8PodLabelsAndAnnotationsSupplier implements Function { + + private final KubernetesClient client; + + private final String namespace; + + Fabric8PodLabelsAndAnnotationsSupplier(KubernetesClient client, String namespace) { + this.client = client; + this.namespace = namespace; + } + + @Override + public PodLabelsAndAnnotations apply(String podName) { + ObjectMeta metadata = Optional.ofNullable(client.pods().inNamespace(namespace).withName(podName).get()) + .map(Pod::getMetadata).orElse(new ObjectMeta()); + return new PodLabelsAndAnnotations(metadata.getLabels(), metadata.getAnnotations()); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClient.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClient.java index 3e660777..778f40a3 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClient.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClient.java @@ -35,18 +35,19 @@ import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.discovery.DiscoveryClientUtils; import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.cloud.kubernetes.commons.discovery.ServiceMetadataForServiceInstance; import org.springframework.cloud.kubernetes.commons.discovery.ServicePortNameAndNumber; import org.springframework.cloud.kubernetes.commons.discovery.ServicePortSecureResolver; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; import org.springframework.core.log.LogAccessor; +import static org.springframework.cloud.kubernetes.commons.discovery.DiscoveryClientUtils.serviceInstance; import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.EXTERNAL_NAME; import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8KubernetesDiscoveryClientUtils.addresses; import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8KubernetesDiscoveryClientUtils.endpointSubsetPortsData; import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8KubernetesDiscoveryClientUtils.endpoints; import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8KubernetesDiscoveryClientUtils.portsData; -import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8KubernetesDiscoveryClientUtils.serviceInstance; import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8KubernetesDiscoveryClientUtils.services; /** @@ -128,9 +129,15 @@ public class KubernetesDiscoveryClient implements DiscoveryClient, EnvironmentAw serviceMetadata.getLabels(), serviceMetadata.getAnnotations(), Map.of(), properties, serviceMetadata.getNamespace(), service.getSpec().getType()); - ServiceInstance externalNameServiceInstance = serviceInstance(null, service, null, - new ServicePortNameAndNumber(-1, null), serviceId, result, service.getMetadata().getNamespace(), - properties, client); + ServiceMetadataForServiceInstance forServiceInstance = new ServiceMetadataForServiceInstance( + service.getMetadata().getName(), service.getMetadata().getLabels(), + service.getMetadata().getAnnotations()); + + ServiceInstance externalNameServiceInstance = serviceInstance(null, forServiceInstance, + new Fabric8InstanceIdHostPodNameSupplier(null, service), + new Fabric8PodLabelsAndAnnotationsSupplier(null, null), new ServicePortNameAndNumber(-1, null), + serviceId, result, service.getMetadata().getNamespace(), properties); + instances.add(externalNameServiceInstance); } } @@ -167,8 +174,15 @@ public class KubernetesDiscoveryClient implements DiscoveryClient, EnvironmentAw List addresses = addresses(endpointSubset, properties); for (EndpointAddress endpointAddress : addresses) { - ServiceInstance serviceInstance = serviceInstance(servicePortSecureResolver, service, endpointAddress, - portData, serviceId, result, namespace, properties, client); + + ServiceMetadataForServiceInstance forServiceInstance = new ServiceMetadataForServiceInstance( + service.getMetadata().getName(), service.getMetadata().getLabels(), + service.getMetadata().getAnnotations()); + + ServiceInstance serviceInstance = serviceInstance(servicePortSecureResolver, forServiceInstance, + new Fabric8InstanceIdHostPodNameSupplier(endpointAddress, service), + new Fabric8PodLabelsAndAnnotationsSupplier(client, namespace), portData, serviceId, result, + namespace, properties); instances.add(serviceInstance); } } diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8InstanceIdHostPodNameSupplierTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8InstanceIdHostPodNameSupplierTests.java new file mode 100644 index 00000000..24bb82e0 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8InstanceIdHostPodNameSupplierTests.java @@ -0,0 +1,128 @@ +/* + * 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.fabric8.discovery; + +import io.fabric8.kubernetes.api.model.EndpointAddress; +import io.fabric8.kubernetes.api.model.EndpointAddressBuilder; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.ServiceBuilder; +import io.fabric8.kubernetes.api.model.ServiceSpecBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.cloud.kubernetes.commons.discovery.InstanceIdHostPodName; + +/** + * @author wind57 + */ +class Fabric8InstanceIdHostPodNameSupplierTests { + + @Test + void instanceIdNoEndpointAddress() { + EndpointAddress endpointAddress = null; + Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().build()) + .withMetadata(new ObjectMetaBuilder().withUid("123").build()).build(); + + Fabric8InstanceIdHostPodNameSupplier supplier = new Fabric8InstanceIdHostPodNameSupplier(endpointAddress, + service); + InstanceIdHostPodName result = supplier.get(); + + Assertions.assertNotNull(result); + Assertions.assertEquals(result.instanceId(), "123"); + } + + @Test + void instanceIdWithEndpointAddress() { + EndpointAddress endpointAddress = new EndpointAddressBuilder() + .withTargetRef(new ObjectReferenceBuilder().withUid("456").build()).build(); + Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().build()) + .withMetadata(new ObjectMetaBuilder().withUid("123").build()).build(); + + Fabric8InstanceIdHostPodNameSupplier supplier = new Fabric8InstanceIdHostPodNameSupplier(endpointAddress, + service); + InstanceIdHostPodName result = supplier.get(); + + Assertions.assertNotNull(result); + Assertions.assertEquals(result.instanceId(), "456"); + } + + @Test + void hostNoEndpointAddress() { + EndpointAddress endpointAddress = null; + Service service = new ServiceBuilder() + .withSpec(new ServiceSpecBuilder().withExternalName("external-name").build()) + .withMetadata(new ObjectMeta()).build(); + + Fabric8InstanceIdHostPodNameSupplier supplier = new Fabric8InstanceIdHostPodNameSupplier(endpointAddress, + service); + InstanceIdHostPodName result = supplier.get(); + + Assertions.assertNotNull(result); + Assertions.assertEquals(result.host(), "external-name"); + } + + @Test + void hostWithEndpointAddress() { + EndpointAddress endpointAddress = new EndpointAddressBuilder().withIp("127.0.0.1").build(); + Service service = new ServiceBuilder() + .withSpec(new ServiceSpecBuilder().withExternalName("external-name").build()) + .withMetadata(new ObjectMeta()).build(); + + Fabric8InstanceIdHostPodNameSupplier supplier = new Fabric8InstanceIdHostPodNameSupplier(endpointAddress, + service); + InstanceIdHostPodName result = supplier.get(); + + Assertions.assertNotNull(result); + Assertions.assertEquals(result.host(), "127.0.0.1"); + } + + @Test + void podNameKindNotPod() { + EndpointAddress endpointAddress = new EndpointAddressBuilder() + .withTargetRef(new ObjectReferenceBuilder().withKind("Service").build()).build(); + Service service = new ServiceBuilder() + .withSpec(new ServiceSpecBuilder().withExternalName("external-name").build()) + .withMetadata(new ObjectMeta()).build(); + + Fabric8InstanceIdHostPodNameSupplier supplier = new Fabric8InstanceIdHostPodNameSupplier(endpointAddress, + service); + InstanceIdHostPodName result = supplier.get(); + + Assertions.assertNotNull(result); + Assertions.assertNull(result.podName()); + } + + @Test + void podNameKindIsPod() { + EndpointAddress endpointAddress = new EndpointAddressBuilder() + .withTargetRef(new ObjectReferenceBuilder().withKind("Pod").withName("my-pod").build()).build(); + Service service = new ServiceBuilder() + .withSpec(new ServiceSpecBuilder().withExternalName("external-name").build()) + .withMetadata(new ObjectMeta()).build(); + + Fabric8InstanceIdHostPodNameSupplier supplier = new Fabric8InstanceIdHostPodNameSupplier(endpointAddress, + service); + InstanceIdHostPodName result = supplier.get(); + + Assertions.assertNotNull(result); + Assertions.assertEquals(result.podName(), "my-pod"); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8PodLabelsAndAnnotationsSupplierTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8PodLabelsAndAnnotationsSupplierTests.java new file mode 100644 index 00000000..db530b53 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8PodLabelsAndAnnotationsSupplierTests.java @@ -0,0 +1,72 @@ +/* + * 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.fabric8.discovery; + +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.PodBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.cloud.kubernetes.commons.discovery.PodLabelsAndAnnotations; + +/** + * @author wind57 + */ +@EnableKubernetesMockClient(crud = true, https = false) +class Fabric8PodLabelsAndAnnotationsSupplierTests { + + private static final String NAMESPACE = "spring-k8s"; + + private static final String POD_NAME = "my-pod"; + + private static KubernetesClient client; + + @AfterEach + void afterEach() { + client.pods().inAnyNamespace().delete(); + } + + @Test + void noObjetMeta() { + client.pods().inNamespace(NAMESPACE) + .resource(new PodBuilder().withMetadata(new ObjectMetaBuilder().withName(POD_NAME).build()).build()) + .create(); + + PodLabelsAndAnnotations result = new Fabric8PodLabelsAndAnnotationsSupplier(client, NAMESPACE).apply(POD_NAME); + Assertions.assertNotNull(result); + Assertions.assertTrue(result.labels().isEmpty()); + Assertions.assertTrue(result.annotations().isEmpty()); + } + + @Test + void labelsAndAnnotationsPresent() { + client.pods().inNamespace(NAMESPACE).resource(new PodBuilder().withMetadata(new ObjectMetaBuilder() + .withName(POD_NAME).withLabels(Map.of("a", "b")).withAnnotations(Map.of("c", "d")).build()).build()) + .create(); + + PodLabelsAndAnnotations result = new Fabric8PodLabelsAndAnnotationsSupplier(client, NAMESPACE).apply(POD_NAME); + Assertions.assertNotNull(result); + Assertions.assertEquals(result.labels(), Map.of("a", "b")); + Assertions.assertEquals(result.annotations(), Map.of("c", "d")); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientUtilsPodMetadataTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientUtilsPodMetadataTests.java deleted file mode 100644 index 8d8505f9..00000000 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientUtilsPodMetadataTests.java +++ /dev/null @@ -1,213 +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.fabric8.discovery; - -import java.util.Map; -import java.util.Set; - -import io.fabric8.kubernetes.api.model.EndpointAddress; -import io.fabric8.kubernetes.api.model.EndpointAddressBuilder; -import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder; -import io.fabric8.kubernetes.api.model.PodBuilder; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; -import org.junit.jupiter.api.AfterEach; -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; - -import static org.springframework.cloud.kubernetes.fabric8.discovery.Fabric8KubernetesDiscoveryClientUtils.podMetadata; - -/** - * @author wind57 - */ -@ExtendWith(OutputCaptureExtension.class) -@EnableKubernetesMockClient(https = false, crud = true) -class KubernetesDiscoveryClientUtilsPodMetadataTests { - - private KubernetesClient client; - - @AfterEach - void afterEach() { - client.pods().inAnyNamespace().delete(); - } - - /** - * service is of type ExternalName, thus no podMetadata is added. - */ - @Test - void testExternalName() { - Map serviceMetadata = Map.of("type", "ExternalName"); - boolean addPodLabels = true; - boolean addPodAnnotations = true; - KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, - "", false, "", addPodLabels, addPodAnnotations); - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - false, "", Set.of(), Map.of(), "", metadata, 0, false); - EndpointAddress endpointAddress = new EndpointAddressBuilder().build(); - String namespace = "default"; - - Map> result = podMetadata(client, serviceMetadata, properties, endpointAddress, - namespace); - Assertions.assertEquals(result, Map.of()); - } - - /** - * service is not of type ExternalName, but neither podLabels nor podAnnotations are - * requested. As such, podMetadata is empty. - */ - @Test - void testNotExternalName() { - Map serviceMetadata = Map.of("type", "ClusterIP"); - boolean addPodLabels = false; - boolean addPodAnnotations = false; - KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, - "", false, "", addPodLabels, addPodAnnotations); - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - false, "", Set.of(), Map.of(), "", metadata, 0, false); - EndpointAddress endpointAddress = new EndpointAddressBuilder().build(); - String namespace = "default"; - - Map> result = podMetadata(client, serviceMetadata, properties, endpointAddress, - namespace); - Assertions.assertEquals(result, Map.of()); - } - - /** - * service is not of type ExternalName, and podLabels are requested, but a pod does - * not exist. As such pod metadata is empty. - */ - @Test - void testNotExternalPodNotPresent() { - Map serviceMetadata = Map.of("type", "ClusterIP"); - boolean addPodLabels = true; - boolean addPodAnnotations = false; - String podName = "my-pod"; - KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, - "", false, "", addPodLabels, addPodAnnotations); - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - false, "", Set.of(), Map.of(), "", metadata, 0, false); - EndpointAddress endpointAddress = new EndpointAddressBuilder() - .withTargetRef(new ObjectReferenceBuilder().withKind("Pod").withName(podName).build()).build(); - String namespace = "default"; - - Map> result = podMetadata(client, serviceMetadata, properties, endpointAddress, - namespace); - Assertions.assertEquals(result, Map.of()); - } - - /** - * service is not of type ExternalName, and podLabels are requested. As such, - * podMetadata contains only pod labels. - */ - @Test - void testNotExternalNamePodLabelsRequested(CapturedOutput output) { - Map serviceMetadata = Map.of("type", "ClusterIP"); - boolean addPodLabels = true; - boolean addPodAnnotations = false; - String podName = "my-pod"; - KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, - "", false, "", addPodLabels, addPodAnnotations); - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - false, "", Set.of(), Map.of(), "", metadata, 0, false); - EndpointAddress endpointAddress = new EndpointAddressBuilder() - .withTargetRef(new ObjectReferenceBuilder().withKind("Pod").withName(podName).build()).build(); - String namespace = "default"; - - client.pods().inNamespace(namespace) - .resource(new PodBuilder().withNewMetadata().withName(podName) - .withLabels(Map.of("label-key", "label-value")) - .withAnnotations(Map.of("annotation-key", "annotation-value")).and().build()) - .create(); - - Map> result = podMetadata(client, serviceMetadata, properties, endpointAddress, - namespace); - Assertions.assertEquals(result.get("labels"), Map.of("label-key", "label-value")); - Assertions.assertNull(result.get("annotations")); - Assertions.assertTrue( - output.getOut().contains("adding podMetadata : {labels={label-key=label-value}} from pod : my-pod")); - } - - /** - * service is not of type ExternalName, and podAnnotations are requested. As such, - * podMetadata contains only pod annotations. - */ - @Test - void testNotExternalNamePodAnnotationsRequested(CapturedOutput output) { - Map serviceMetadata = Map.of("type", "ClusterIP"); - boolean addPodLabels = false; - boolean addPodAnnotations = true; - String podName = "my-pod"; - KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, - "", false, "", addPodLabels, addPodAnnotations); - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - false, "", Set.of(), Map.of(), "", metadata, 0, false); - EndpointAddress endpointAddress = new EndpointAddressBuilder() - .withTargetRef(new ObjectReferenceBuilder().withKind("Pod").withName(podName).build()).build(); - String namespace = "default"; - - client.pods().inNamespace(namespace) - .resource(new PodBuilder().withNewMetadata().withName(podName) - .withLabels(Map.of("label-key", "label-value")) - .withAnnotations(Map.of("annotation-key", "annotation-value")).and().build()) - .create(); - - Map> result = podMetadata(client, serviceMetadata, properties, endpointAddress, - namespace); - Assertions.assertNull(result.get("labels")); - Assertions.assertEquals(result.get("annotations"), Map.of("annotation-key", "annotation-value")); - Assertions.assertTrue(output.getOut() - .contains("adding podMetadata : {annotations={annotation-key=annotation-value}} from pod : my-pod")); - } - - /** - * service is not of type ExternalName, both podLabels and podAnnotations are - * requested. As such, podMetadata contains both. - */ - @Test - void testNotExternalNamePodLabelsAndAnnotationsRequested(CapturedOutput output) { - Map serviceMetadata = Map.of("type", "ClusterIP"); - boolean addPodLabels = true; - boolean addPodAnnotations = true; - String podName = "my-pod"; - KubernetesDiscoveryProperties.Metadata metadata = new KubernetesDiscoveryProperties.Metadata(false, "", false, - "", false, "", addPodLabels, addPodAnnotations); - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - false, "", Set.of(), Map.of(), "", metadata, 0, false); - EndpointAddress endpointAddress = new EndpointAddressBuilder() - .withTargetRef(new ObjectReferenceBuilder().withKind("Pod").withName(podName).build()).build(); - String namespace = "default"; - - client.pods().inNamespace(namespace) - .resource(new PodBuilder().withNewMetadata().withName(podName) - .withLabels(Map.of("label-key", "label-value")) - .withAnnotations(Map.of("annotation-key", "annotation-value")).and().build()) - .create(); - - Map> result = podMetadata(client, serviceMetadata, properties, endpointAddress, - namespace); - Assertions.assertEquals(result.get("labels"), Map.of("label-key", "label-value")); - Assertions.assertEquals(result.get("annotations"), Map.of("annotation-key", "annotation-value")); - Assertions.assertTrue(output.getOut().contains( - "adding podMetadata : {annotations={annotation-key=annotation-value}, labels={label-key=label-value}} from pod : my-pod")); - } - -} diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientUtilsTests.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientUtilsTests.java index 277bbf24..96d9c763 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientUtilsTests.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/KubernetesDiscoveryClientUtilsTests.java @@ -27,21 +27,13 @@ import io.fabric8.kubernetes.api.model.EndpointSubset; import io.fabric8.kubernetes.api.model.EndpointSubsetBuilder; import io.fabric8.kubernetes.api.model.Endpoints; import io.fabric8.kubernetes.api.model.EndpointsBuilder; -import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; -import io.fabric8.kubernetes.api.model.Service; -import io.fabric8.kubernetes.api.model.ServiceBuilder; -import io.fabric8.kubernetes.api.model.ServiceSpecBuilder; 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.client.ServiceInstance; -import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance; import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; -import org.springframework.cloud.kubernetes.commons.discovery.ServicePortNameAndNumber; -import org.springframework.cloud.kubernetes.commons.discovery.ServicePortSecureResolver; /** * @author wind57 @@ -150,116 +142,4 @@ class KubernetesDiscoveryClientUtilsTests { Assertions.assertEquals(hostNames, List.of("one", "three", "two")); } - @Test - void testServiceInstance() { - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - false, "", Set.of(), Map.of(), "", KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false, false); - ServicePortSecureResolver resolver = new ServicePortSecureResolver(properties); - Service service = new ServiceBuilder().withMetadata(new ObjectMeta()).build(); - EndpointAddress address = new EndpointAddressBuilder().withNewTargetRef().withUid("123").endTargetRef() - .withIp("127.0.0.1").build(); - - ServicePortNameAndNumber portData = new ServicePortNameAndNumber(8080, "http"); - ServiceInstance serviceInstance = Fabric8KubernetesDiscoveryClientUtils.serviceInstance(resolver, service, - address, portData, "my-service", Map.of("a", "b"), "k8s", properties, null); - Assertions.assertTrue(serviceInstance instanceof DefaultKubernetesServiceInstance); - DefaultKubernetesServiceInstance defaultInstance = (DefaultKubernetesServiceInstance) serviceInstance; - Assertions.assertEquals(defaultInstance.getInstanceId(), "123"); - Assertions.assertEquals(defaultInstance.getServiceId(), "my-service"); - Assertions.assertEquals(defaultInstance.getHost(), "127.0.0.1"); - Assertions.assertEquals(defaultInstance.getPort(), 8080); - Assertions.assertFalse(defaultInstance.isSecure()); - Assertions.assertEquals(defaultInstance.getUri().toASCIIString(), "http://127.0.0.1:8080"); - Assertions.assertEquals(defaultInstance.getMetadata(), Map.of("a", "b")); - Assertions.assertEquals(defaultInstance.getScheme(), "http"); - Assertions.assertEquals(defaultInstance.getNamespace(), "k8s"); - Assertions.assertNull(defaultInstance.getCluster()); - } - - @Test - void testExternalNameServiceInstance() { - Service service = new ServiceBuilder() - .withSpec(new ServiceSpecBuilder().withExternalName("spring.io").withType("ExternalName").build()) - .withMetadata(new ObjectMetaBuilder().withUid("123").build()).build(); - - ServicePortNameAndNumber portData = new ServicePortNameAndNumber(-1, "http"); - ServiceInstance serviceInstance = Fabric8KubernetesDiscoveryClientUtils.serviceInstance(null, service, null, - portData, "my-service", Map.of("a", "b"), "k8s", KubernetesDiscoveryProperties.DEFAULT, null); - Assertions.assertTrue(serviceInstance instanceof DefaultKubernetesServiceInstance); - DefaultKubernetesServiceInstance defaultInstance = (DefaultKubernetesServiceInstance) serviceInstance; - Assertions.assertEquals(defaultInstance.getInstanceId(), "123"); - Assertions.assertEquals(defaultInstance.getServiceId(), "my-service"); - Assertions.assertEquals(defaultInstance.getHost(), "spring.io"); - Assertions.assertEquals(defaultInstance.getPort(), -1); - Assertions.assertFalse(defaultInstance.isSecure()); - Assertions.assertEquals(defaultInstance.getUri().toASCIIString(), "spring.io"); - Assertions.assertEquals(defaultInstance.getMetadata(), Map.of("a", "b")); - Assertions.assertEquals(defaultInstance.getScheme(), "http"); - Assertions.assertEquals(defaultInstance.getNamespace(), "k8s"); - Assertions.assertNull(defaultInstance.getCluster()); - } - - @Test - void testNoPortsServiceInstance() { - Service service = new ServiceBuilder().withSpec(new ServiceSpecBuilder().withType("ClusterIP").build()) - .withMetadata(new ObjectMetaBuilder().withUid("123").build()).build(); - - EndpointAddress endpointAddress = new EndpointAddressBuilder().withIp("127.0.0.1").build(); - - ServicePortNameAndNumber portData = new ServicePortNameAndNumber(0, "http"); - ServiceInstance serviceInstance = Fabric8KubernetesDiscoveryClientUtils.serviceInstance(null, service, - endpointAddress, portData, "my-service", Map.of("a", "b"), "k8s", KubernetesDiscoveryProperties.DEFAULT, - null); - Assertions.assertTrue(serviceInstance instanceof DefaultKubernetesServiceInstance); - DefaultKubernetesServiceInstance defaultInstance = (DefaultKubernetesServiceInstance) serviceInstance; - Assertions.assertEquals(defaultInstance.getInstanceId(), "123"); - Assertions.assertEquals(defaultInstance.getServiceId(), "my-service"); - Assertions.assertEquals(defaultInstance.getHost(), "127.0.0.1"); - Assertions.assertEquals(defaultInstance.getScheme(), "http"); - Assertions.assertEquals(defaultInstance.getPort(), 0); - Assertions.assertFalse(defaultInstance.isSecure()); - Assertions.assertEquals(defaultInstance.getUri().toASCIIString(), "http://127.0.0.1"); - Assertions.assertEquals(defaultInstance.getMetadata(), Map.of("a", "b")); - Assertions.assertEquals(defaultInstance.getNamespace(), "k8s"); - Assertions.assertNull(defaultInstance.getCluster()); - } - - /** - * endpoints ports are empty. - */ - @Test - void testEndpointSubsetPortsDataOne() { - EndpointSubset endpointSubset = new EndpointSubsetBuilder().build(); - Map result = Fabric8KubernetesDiscoveryClientUtils.endpointSubsetPortsData(endpointSubset); - Assertions.assertTrue(result.isEmpty()); - } - - /** - * endpoints ports has one entry. - */ - @Test - void testEndpointSubsetPortsDataTwo() { - EndpointSubset endpointSubset = new EndpointSubsetBuilder() - .withPorts(new EndpointPortBuilder().withPort(8080).withName("http").build()).build(); - Map result = Fabric8KubernetesDiscoveryClientUtils.endpointSubsetPortsData(endpointSubset); - Assertions.assertEquals(result.size(), 1); - Assertions.assertEquals(result.get("http"), 8080); - } - - /** - * endpoints ports has three entries, only two are picked up. - */ - @Test - void testEndpointSubsetPortsDataThree() { - EndpointSubset endpointSubset = new EndpointSubsetBuilder() - .withPorts(new EndpointPortBuilder().withPort(8080).withName("http").build(), - new EndpointPortBuilder().withPort(8081).build(), - new EndpointPortBuilder().withPort(8082).withName("https").build()) - .build(); - Map result = Fabric8KubernetesDiscoveryClientUtils.endpointSubsetPortsData(endpointSubset); - Assertions.assertEquals(result.size(), 2); - Assertions.assertEquals(result.get("http"), 8080); - Assertions.assertEquals(result.get("https"), 8082); - } - }