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 140c05fb..3b2bd0e8 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 @@ -17,14 +17,21 @@ package org.springframework.cloud.kubernetes.commons.discovery; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; import org.apache.commons.logging.LogFactory; 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.HTTP; +import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.HTTPS; import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.NAMESPACE_METADATA_KEY; +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; /** @@ -77,4 +84,95 @@ public final class DiscoveryClientUtils { return serviceMetadata; } + public static ServicePortNameAndNumber endpointsPort(LinkedHashMap endpointsPorts, + String serviceId, KubernetesDiscoveryProperties properties, Map serviceLabels) { + + if (endpointsPorts.size() == 0) { + LOG.debug(() -> "no ports found for service : " + serviceId + ", will return zero"); + return new ServicePortNameAndNumber(0, "http"); + } + + if (endpointsPorts.size() == 1) { + Map.Entry single = endpointsPorts.entrySet().iterator().next(); + LOG.debug(() -> "endpoint ports has a single entry, using port : " + single.getValue()); + return new ServicePortNameAndNumber(single.getValue(), single.getKey()); + } + + else { + + Optional portData; + String primaryPortName = primaryPortName(properties, serviceLabels, serviceId); + + Map existingPorts = endpointsPorts.entrySet().stream() + .filter(entry -> StringUtils.hasText(entry.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + portData = fromMap(existingPorts, primaryPortName, "found primary-port-name (with value: '" + + primaryPortName + "') via properties or service labels to match port"); + if (portData.isPresent()) { + return portData.get(); + } + + portData = fromMap(existingPorts, HTTPS, "found primary-port-name via 'https' to match port"); + if (portData.isPresent()) { + return portData.get(); + } + + portData = fromMap(existingPorts, HTTP, "found primary-port-name via 'http' to match port"); + if (portData.isPresent()) { + return portData.get(); + } + + logWarnings(); + Map.Entry first = endpointsPorts.entrySet().iterator().next(); + return new ServicePortNameAndNumber(first.getValue(), first.getKey()); + + } + } + + /** + * 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, Map serviceLabels, + String serviceId) { + String primaryPortNameFromProperties = properties.primaryPortName(); + + // 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; + } + + private static Optional fromMap(Map existingPorts, String key, + String message) { + Integer fromPrimaryPortName = existingPorts.get(key); + if (fromPrimaryPortName == null) { + LOG.debug(() -> "not " + message); + return Optional.empty(); + } + else { + LOG.debug(() -> message + " : " + fromPrimaryPortName); + return Optional.of(new ServicePortNameAndNumber(fromPrimaryPortName, key)); + } + } + + private static void logWarnings() { + LOG.warn(() -> """ + Make sure that either the primary-port-name label has been added to the service, + or spring.cloud.kubernetes.discovery.primary-port-name has been configured. + Alternatively name the primary port 'https' or 'http' + An incorrect configuration may result in non-deterministic behaviour."""); + } + } diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ServicePortData.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/ServicePortNameAndNumber.java similarity index 75% rename from spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ServicePortData.java rename to spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/ServicePortNameAndNumber.java index 3016900f..7e88d98c 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/Fabric8ServicePortData.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/discovery/ServicePortNameAndNumber.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2019-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. @@ -14,10 +14,10 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.discovery; +package org.springframework.cloud.kubernetes.commons.discovery; /** * @author wind57 */ -record Fabric8ServicePortData(int portNumber, String portName) { +public record ServicePortNameAndNumber(int portNumber, String portName) { } 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 cf6d923a..d25d576a 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 @@ -16,6 +16,7 @@ package org.springframework.cloud.kubernetes.commons.discovery; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -27,6 +28,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; +import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.PRIMARY_PORT_NAME_LABEL_KEY; + /** * @author wind57 */ @@ -347,6 +350,282 @@ class DiscoveryClientUtilsTests { .contains("Adding port metadata: {prefix-http=8081, prefix-https=8080} for serviceId : my-service")); } + /** + *
+	 *     - properties do not have primary-port-name set
+	 *     - service labels do not have primary-port-name set
+	 *
+	 *     As such null is returned.
+	 * 
+ */ + @Test + void testPrimaryPortNameNotFound(CapturedOutput output) { + KubernetesDiscoveryProperties properties = KubernetesDiscoveryProperties.DEFAULT; + + Map serviceLabels = Map.of(); + + String result = DiscoveryClientUtils.primaryPortName(properties, serviceLabels, "abc"); + Assertions.assertNull(result); + Assertions.assertTrue(output.getOut().contains( + "did not find a primary-port-name in neither properties nor service labels for service with ID : abc")); + } + + /** + *
+	 *     - properties do have primary-port-name set to "https"
+	 *     - service labels do not have primary-port-name set
+	 *
+	 *     As such "https" is returned.
+	 * 
+ */ + @Test + void testPrimaryPortNameFoundInProperties(CapturedOutput output) { + String primaryPortName = "https"; + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + true, "", Set.of(), Map.of(), primaryPortName, null, 0, false); + + Map serviceLabels = Map.of(); + + String result = DiscoveryClientUtils.primaryPortName(properties, serviceLabels, "abc"); + Assertions.assertNotNull(result); + Assertions.assertEquals(result, primaryPortName); + Assertions.assertTrue(output.getOut().contains("will use primaryPortName : https for service with ID = abc")); + } + + /** + *
+	 *     - properties do not have primary-port-name set
+	 *     - service labels do have primary-port-name set to "https"
+	 *
+	 *     As such "https" is returned.
+	 * 
+ */ + @Test + void testPrimaryPortNameFoundInLabels(CapturedOutput output) { + Map serviceLabels = Map.of(PRIMARY_PORT_NAME_LABEL_KEY, "https"); + KubernetesDiscoveryProperties properties = KubernetesDiscoveryProperties.DEFAULT; + + String result = DiscoveryClientUtils.primaryPortName(properties, serviceLabels, "abc"); + Assertions.assertNotNull(result); + Assertions.assertEquals(result, "https"); + Assertions.assertTrue(output.getOut().contains("will use primaryPortName : https for service with ID = abc")); + } + + /** + *
+	 *     - properties do have primary-port-name set to "https"
+	 *     - service labels do have primary-port-name set to "http"
+	 *
+	 *     As such "http" is returned (labels win).
+	 * 
+ */ + @Test + void testPrimaryPortNameFoundInBothPropertiesAndLabels(CapturedOutput output) { + String primaryPortName = "https"; + Map serviceLabels = Map.of(PRIMARY_PORT_NAME_LABEL_KEY, "http"); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + true, "", Set.of(), Map.of(), primaryPortName, null, 0, false); + + String result = DiscoveryClientUtils.primaryPortName(properties, serviceLabels, "abc"); + Assertions.assertNotNull(result); + Assertions.assertEquals(result, "http"); + Assertions.assertTrue(output.getOut().contains("will use primaryPortName : http for service with ID = abc")); + } + + /** + *
+	 *     - EndpointSubset has no ports.
+	 * 
+ */ + @Test + void testEndpointsPortNoPorts(CapturedOutput output) { + String serviceId = "spring-k8s"; + KubernetesDiscoveryProperties properties = KubernetesDiscoveryProperties.DEFAULT; + + LinkedHashMap endpointsPorts = new LinkedHashMap<>(); + Map serviceLabels = Map.of(); + + ServicePortNameAndNumber portData = DiscoveryClientUtils.endpointsPort(endpointsPorts, serviceId, properties, + serviceLabels); + Assertions.assertEquals(portData.portNumber(), 0); + Assertions.assertEquals(portData.portName(), "http"); + Assertions.assertTrue(output.getOut().contains("no ports found for service : spring-k8s, will return zero")); + } + + /** + *
+	 *     - EndpointSubset has a single entry in getPorts.
+	 * 
+ */ + @Test + void testEndpointsPortSinglePort(CapturedOutput output) { + String serviceId = "spring-k8s"; + KubernetesDiscoveryProperties properties = KubernetesDiscoveryProperties.DEFAULT; + + LinkedHashMap endpointsPorts = new LinkedHashMap<>(); + endpointsPorts.put("http", 8080); + Map serviceLabels = Map.of(); + + ServicePortNameAndNumber portData = DiscoveryClientUtils.endpointsPort(endpointsPorts, serviceId, properties, + serviceLabels); + Assertions.assertEquals(portData.portNumber(), 8080); + Assertions.assertEquals(portData.portName(), "http"); + Assertions.assertTrue(output.getOut().contains("endpoint ports has a single entry, using port : 8080")); + } + + /** + *
+	 *     - primary-port-name is null.
+	 * 
+ */ + @Test + void testEndpointsPortNullPrimaryPortName(CapturedOutput output) { + String serviceId = "spring-k8s"; + KubernetesDiscoveryProperties properties = KubernetesDiscoveryProperties.DEFAULT; + + LinkedHashMap endpointsPorts = new LinkedHashMap<>(); + endpointsPorts.put(null, 8080); + endpointsPorts.put("not-http-or-https", 8081); + Map serviceLabels = Map.of(); + + ServicePortNameAndNumber portData = DiscoveryClientUtils.endpointsPort(endpointsPorts, serviceId, properties, + serviceLabels); + Assertions.assertEquals(portData.portNumber(), 8080); + Assertions.assertNull(portData.portName()); + Assertions.assertTrue(output.getOut().contains( + "did not find a primary-port-name in neither properties nor service labels for service with ID : spring-k8s")); + Assertions.assertTrue(output.getOut() + .contains("not found primary-port-name (with value: 'null') via properties or service labels")); + Assertions.assertTrue(output.getOut().contains("not found primary-port-name via 'https' to match port")); + Assertions.assertTrue(output.getOut().contains("not found primary-port-name via 'http' to match port")); + Assertions.assertTrue(output.getOut().contains(""" + Make sure that either the primary-port-name label has been added to the service, + or spring.cloud.kubernetes.discovery.primary-port-name has been configured. + Alternatively name the primary port 'https' or 'http' + An incorrect configuration may result in non-deterministic behaviour.""")); + } + + /** + *
+	 *     - primary-port-name is "three", such a port name does not exist.
+	 * 
+ */ + @Test + void testEndpointsPortPrimaryPortNameIsPresentButNotFound(CapturedOutput output) { + String serviceId = "spring-k8s"; + String primaryPortName = "three"; + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + true, "", Set.of(), Map.of(), primaryPortName, null, 0, false); + + LinkedHashMap endpointsPorts = new LinkedHashMap<>(); + endpointsPorts.put("one", 8080); + endpointsPorts.put("two", 8081); + Map serviceLabels = Map.of(); + + ServicePortNameAndNumber portData = DiscoveryClientUtils.endpointsPort(endpointsPorts, serviceId, properties, + serviceLabels); + Assertions.assertEquals(portData.portNumber(), 8080); + Assertions.assertEquals(portData.portName(), "one"); + Assertions.assertTrue( + output.getOut().contains("will use primaryPortName : three for service with ID = spring-k8s")); + Assertions.assertTrue(output.getOut() + .contains("not found primary-port-name (with value: 'three') via properties or service labels")); + Assertions.assertTrue(output.getOut().contains("not found primary-port-name via 'https' to match port")); + Assertions.assertTrue(output.getOut().contains("not found primary-port-name via 'http' to match port")); + Assertions.assertTrue(output.getOut().contains(""" + Make sure that either the primary-port-name label has been added to the service, + or spring.cloud.kubernetes.discovery.primary-port-name has been configured. + Alternatively name the primary port 'https' or 'http' + An incorrect configuration may result in non-deterministic behaviour.""")); + } + + /** + *
+	 *     - primary-port-name is "two", such a port name exists and matches 8081
+	 * 
+ */ + @Test + void testEndpointsPortPrimaryPortNameFound(CapturedOutput output) { + String serviceId = "spring-k8s"; + String primaryPortName = "two"; + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + true, "", Set.of(), Map.of(), primaryPortName, null, 0, false); + + LinkedHashMap endpointsPorts = new LinkedHashMap<>(); + endpointsPorts.put("one", 8080); + endpointsPorts.put("two", 8081); + Map serviceLabels = Map.of(); + + ServicePortNameAndNumber portData = DiscoveryClientUtils.endpointsPort(endpointsPorts, serviceId, properties, + serviceLabels); + Assertions.assertEquals(portData.portNumber(), 8081); + Assertions.assertEquals(portData.portName(), "two"); + Assertions.assertTrue( + output.getOut().contains("will use primaryPortName : two for service with ID = spring-k8s")); + Assertions.assertTrue(output.getOut().contains( + "found primary-port-name (with value: 'two') via properties or service labels to match port : 8081")); + } + + /** + *
+	 *     - primary-port-name is "three", such a port name does not exist.
+	 *     - https port exists and this one is returned
+	 * 
+ */ + @Test + void testEndpointsPortPrimaryPortHttps(CapturedOutput output) { + String serviceId = "spring-k8s"; + String primaryPortName = "three"; + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + true, "", Set.of(), Map.of(), primaryPortName, null, 0, false, false); + + LinkedHashMap endpointsPorts = new LinkedHashMap<>(); + endpointsPorts.put("one", 8080); + endpointsPorts.put("two", 8081); + endpointsPorts.put("https", 8082); + Map serviceLabels = Map.of(); + + ServicePortNameAndNumber portData = DiscoveryClientUtils.endpointsPort(endpointsPorts, serviceId, properties, + serviceLabels); + Assertions.assertEquals(portData.portNumber(), 8082); + Assertions.assertEquals(portData.portName(), "https"); + Assertions.assertTrue( + output.getOut().contains("will use primaryPortName : three for service with ID = spring-k8s")); + Assertions.assertTrue(output.getOut().contains( + "not found primary-port-name (with value: 'three') via properties or service labels to match port")); + Assertions.assertTrue(output.getOut().contains("found primary-port-name via 'https' to match port : 8082")); + } + + /** + *
+	 *     - primary-port-name is "three", such a port name does not exist.
+	 *     - http port exists and this one is returned
+	 * 
+ */ + @Test + void testEndpointsPortPrimaryPortHttp(CapturedOutput output) { + String serviceId = "spring-k8s"; + String primaryPortName = "three"; + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + true, "", Set.of(), Map.of(), primaryPortName, null, 0, false); + + LinkedHashMap endpointsPorts = new LinkedHashMap<>(); + endpointsPorts.put("one", 8080); + endpointsPorts.put("two", 8081); + endpointsPorts.put("http", 8082); + Map serviceLabels = Map.of(); + + ServicePortNameAndNumber portData = DiscoveryClientUtils.endpointsPort(endpointsPorts, serviceId, properties, + serviceLabels); + Assertions.assertEquals(portData.portNumber(), 8082); + Assertions.assertEquals(portData.portName(), "http"); + Assertions.assertTrue( + output.getOut().contains("will use primaryPortName : three for service with ID = spring-k8s")); + Assertions.assertTrue(output.getOut().contains( + "not found primary-port-name (with value: 'three') via properties or service labels to match port")); + Assertions.assertTrue(output.getOut().contains("found primary-port-name via 'http' to match port : 8082")); + } + 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-commons/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-commons/src/test/resources/logback-test.xml new file mode 100644 index 00000000..13afbf0c --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/test/resources/logback-test.xml @@ -0,0 +1,18 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + + + + + 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 4df970d2..46878175 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 @@ -18,6 +18,7 @@ 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; import java.util.Optional; @@ -47,14 +48,13 @@ 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.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.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.SERVICE_TYPE; import static org.springframework.cloud.kubernetes.fabric8.discovery.ServicePortSecureResolver.Input; @@ -77,54 +77,6 @@ final class Fabric8KubernetesDiscoveryClientUtils { return new EndpointSubsetNS(endpoints.getMetadata().getNamespace(), endpoints.getSubsets()); } - static Fabric8ServicePortData endpointsPort(EndpointSubset endpointSubset, String serviceId, - KubernetesDiscoveryProperties properties, Service service) { - - List endpointPorts = endpointSubset.getPorts(); - - if (endpointPorts.size() == 0) { - LOG.debug(() -> "no ports found for service : " + serviceId + ", will return zero"); - return new Fabric8ServicePortData(0, "http"); - } - - if (endpointPorts.size() == 1) { - EndpointPort single = endpointPorts.get(0); - int port = single.getPort(); - LOG.debug(() -> "endpoint ports has a single entry, using port : " + port); - return new Fabric8ServicePortData(single.getPort(), single.getName()); - } - - else { - - Optional portData; - String primaryPortName = primaryPortName(properties, service, serviceId); - - Map existingPorts = endpointPorts.stream() - .filter(endpointPort -> StringUtils.hasText(endpointPort.getName())) - .collect(Collectors.toMap(EndpointPort::getName, EndpointPort::getPort)); - - portData = fromMap(existingPorts, primaryPortName, "found primary-port-name (with value: '" - + primaryPortName + "') via properties or service labels to match port"); - if (portData.isPresent()) { - return portData.get(); - } - - portData = fromMap(existingPorts, HTTPS, "found primary-port-name via 'https' to match port"); - if (portData.isPresent()) { - return portData.get(); - } - - portData = fromMap(existingPorts, HTTP, "found primary-port-name via 'http' to match port"); - if (portData.isPresent()) { - return portData.get(); - } - - logWarnings(); - return new Fabric8ServicePortData(endpointPorts.get(0).getPort(), endpointPorts.get(0).getName()); - - } - } - /** * take primary-port-name from service label "PRIMARY_PORT_NAME_LABEL_KEY" if it * exists, otherwise from KubernetesDiscoveryProperties if it exists, otherwise null. @@ -244,7 +196,7 @@ final class Fabric8KubernetesDiscoveryClientUtils { } static ServiceInstance serviceInstance(@Nullable ServicePortSecureResolver servicePortSecureResolver, - Service service, @Nullable EndpointAddress endpointAddress, Fabric8ServicePortData portData, + 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 @@ -336,6 +288,26 @@ final class Fabric8KubernetesDiscoveryClientUtils { .collect(Collectors.toMap(EndpointPort::getName, port -> Integer.toString(port.getPort()))); } + static LinkedHashMap endpointSubsetPortsData(EndpointSubset endpointSubset) { + LinkedHashMap result = new LinkedHashMap<>(); + List endpointPorts = endpointSubset.getPorts(); + + // this is most probably not a needed if statement, but it preserves the + // previous logic before I refactored the code. In particular, this takes care of the fact + // that an EndpointsPort name could be missing. + if (endpointPorts.size() == 1) { + result.put(endpointPorts.get(0).getName(), endpointPorts.get(0).getPort()); + return result; + } + + endpointSubset.getPorts().forEach(port -> { + if (StringUtils.hasText(port.getName())) { + result.put(port.getName(), port.getPort()); + } + }); + return result; + } + /** * serviceName can be null, in which case, such a filter will not be applied. */ @@ -355,25 +327,4 @@ final class Fabric8KubernetesDiscoveryClientUtils { } - private static Optional fromMap(Map existingPorts, String key, - String message) { - Integer fromPrimaryPortName = existingPorts.get(key); - if (fromPrimaryPortName == null) { - LOG.debug(() -> "not " + message); - return Optional.empty(); - } - else { - LOG.debug(() -> message + " : " + fromPrimaryPortName); - return Optional.of(new Fabric8ServicePortData(fromPrimaryPortName, key)); - } - } - - private static void logWarnings() { - LOG.warn(() -> """ - Make sure that either the primary-port-name label has been added to the service, - or spring.cloud.kubernetes.discovery.primary-port-name has been configured. - Alternatively name the primary port 'https' or 'http' - An incorrect configuration may result in non-deterministic behaviour."""); - } - } 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 149d0679..d7d52b26 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,14 +35,15 @@ 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.ServicePortNameAndNumber; 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.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.endpointsPort; 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; @@ -127,7 +128,7 @@ public class KubernetesDiscoveryClient implements DiscoveryClient, EnvironmentAw serviceMetadata.getNamespace(), service.getSpec().getType()); ServiceInstance externalNameServiceInstance = serviceInstance(null, service, null, - new Fabric8ServicePortData(-1, null), serviceId, result, service.getMetadata().getNamespace(), + new ServicePortNameAndNumber(-1, null), serviceId, result, service.getMetadata().getNamespace(), properties, client); instances.add(externalNameServiceInstance); } @@ -159,7 +160,10 @@ public class KubernetesDiscoveryClient implements DiscoveryClient, EnvironmentAw service.getSpec().getType()); for (EndpointSubset endpointSubset : subsets) { - Fabric8ServicePortData portData = endpointsPort(endpointSubset, serviceId, properties, service); + + ServicePortNameAndNumber portData = DiscoveryClientUtils.endpointsPort( + endpointSubsetPortsData(endpointSubset), serviceId, properties, service.getMetadata().getLabels()); + List addresses = addresses(endpointSubset, properties); for (EndpointAddress endpointAddress : addresses) { ServiceInstance serviceInstance = serviceInstance(servicePortSecureResolver, service, endpointAddress, diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/ServicePortSecureResolver.java b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/ServicePortSecureResolver.java index e8c70e9e..cd0e33f8 100644 --- a/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/ServicePortSecureResolver.java +++ b/spring-cloud-kubernetes-fabric8-discovery/src/main/java/org/springframework/cloud/kubernetes/fabric8/discovery/ServicePortSecureResolver.java @@ -23,6 +23,7 @@ import java.util.Set; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.cloud.kubernetes.commons.discovery.ServicePortNameAndNumber; import org.springframework.core.log.LogAccessor; import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.SECURED; @@ -53,7 +54,7 @@ class ServicePortSecureResolver { boolean resolve(Input input) { String serviceName = input.serviceName(); - Fabric8ServicePortData portData = input.portData(); + ServicePortNameAndNumber portData = input.portData(); Optional securedLabelValue = Optional.ofNullable(input.serviceLabels().get(SECURED)); if (securedLabelValue.isPresent() && TRUTHY_STRINGS.contains(securedLabelValue.get())) { @@ -89,10 +90,10 @@ class ServicePortSecureResolver { /** * @author wind57 */ - record Input(Fabric8ServicePortData portData, String serviceName, Map serviceLabels, + record Input(ServicePortNameAndNumber portData, String serviceName, Map serviceLabels, Map serviceAnnotations) { - Input(Fabric8ServicePortData portData, String serviceName, Map serviceLabels, + Input(ServicePortNameAndNumber portData, String serviceName, Map serviceLabels, Map serviceAnnotations) { this.portData = portData; this.serviceName = serviceName; 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 8a42ef27..279053f6 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 @@ -36,13 +36,11 @@ 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.client.ServiceInstance; import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance; import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; - -import static org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryConstants.PRIMARY_PORT_NAME_LABEL_KEY; +import org.springframework.cloud.kubernetes.commons.discovery.ServicePortNameAndNumber; /** * @author wind57 @@ -74,288 +72,6 @@ class KubernetesDiscoveryClientUtilsTests { Assertions.assertEquals(result.namespace(), "default"); } - /** - *
-	 *     - properties do not have primary-port-name set
-	 *     - service labels do not have primary-port-name set
-	 *
-	 *     As such null is returned.
-	 * 
- */ - @Test - void testPrimaryPortNameNotFound(CapturedOutput output) { - KubernetesDiscoveryProperties properties = KubernetesDiscoveryProperties.DEFAULT; - Service service = new ServiceBuilder().withMetadata(new ObjectMeta()).build(); - - String result = Fabric8KubernetesDiscoveryClientUtils.primaryPortName(properties, service, "abc"); - Assertions.assertNull(result); - Assertions.assertTrue(output.getOut().contains( - "did not find a primary-port-name in neither properties nor service labels for service with ID : abc")); - } - - /** - *
-	 *     - properties do have primary-port-name set to "https"
-	 *     - service labels do not have primary-port-name set
-	 *
-	 *     As such "https" is returned.
-	 * 
- */ - @Test - void testPrimaryPortNameFoundInProperties(CapturedOutput output) { - String primaryPortName = "https"; - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - true, "", Set.of(), Map.of(), primaryPortName, null, 0, false); - Service service = new ServiceBuilder().withMetadata(new ObjectMeta()).build(); - - String result = Fabric8KubernetesDiscoveryClientUtils.primaryPortName(properties, service, "abc"); - Assertions.assertNotNull(result); - Assertions.assertEquals(result, primaryPortName); - Assertions.assertTrue(output.getOut().contains("will use primaryPortName : https for service with ID = abc")); - } - - /** - *
-	 *     - properties do not have primary-port-name set
-	 *     - service labels do have primary-port-name set to "https"
-	 *
-	 *     As such "https" is returned.
-	 * 
- */ - @Test - void testPrimaryPortNameFoundInLabels(CapturedOutput output) { - Map labels = Map.of(PRIMARY_PORT_NAME_LABEL_KEY, "https"); - KubernetesDiscoveryProperties properties = KubernetesDiscoveryProperties.DEFAULT; - Service service = new ServiceBuilder().withMetadata(new ObjectMetaBuilder().withLabels(labels).build()).build(); - - String result = Fabric8KubernetesDiscoveryClientUtils.primaryPortName(properties, service, "abc"); - Assertions.assertNotNull(result); - Assertions.assertEquals(result, "https"); - Assertions.assertTrue(output.getOut().contains("will use primaryPortName : https for service with ID = abc")); - } - - /** - *
-	 *     - properties do have primary-port-name set to "https"
-	 *     - service labels do have primary-port-name set to "http"
-	 *
-	 *     As such "http" is returned (labels win).
-	 * 
- */ - @Test - void testPrimaryPortNameFoundInBothPropertiesAndLabels(CapturedOutput output) { - String primaryPortName = "https"; - Map labels = Map.of(PRIMARY_PORT_NAME_LABEL_KEY, "http"); - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - true, "", Set.of(), Map.of(), primaryPortName, null, 0, false); - Service service = new ServiceBuilder().withMetadata(new ObjectMetaBuilder().withLabels(labels).build()).build(); - - String result = Fabric8KubernetesDiscoveryClientUtils.primaryPortName(properties, service, "abc"); - Assertions.assertNotNull(result); - Assertions.assertEquals(result, "http"); - Assertions.assertTrue(output.getOut().contains("will use primaryPortName : http for service with ID = abc")); - } - - /** - *
-	 *     - EndpointSubset has no ports.
-	 * 
- */ - @Test - void testEndpointsPortNoPorts(CapturedOutput output) { - EndpointSubset endpointSubset = new EndpointSubsetBuilder().build(); - String serviceId = "spring-k8s"; - KubernetesDiscoveryProperties properties = KubernetesDiscoveryProperties.DEFAULT; - Service service = new ServiceBuilder().build(); - - Fabric8ServicePortData portData = Fabric8KubernetesDiscoveryClientUtils.endpointsPort(endpointSubset, serviceId, - properties, service); - Assertions.assertEquals(portData.portNumber(), 0); - Assertions.assertEquals(portData.portName(), "http"); - Assertions.assertTrue(output.getOut().contains("no ports found for service : spring-k8s, will return zero")); - } - - /** - *
-	 *     - EndpointSubset has a single entry in getPorts.
-	 * 
- */ - @Test - void testEndpointsPortSinglePort(CapturedOutput output) { - EndpointSubset endpointSubset = new EndpointSubsetBuilder() - .withPorts(new EndpointPortBuilder().withPort(8080).withName("http").build()).build(); - String serviceId = "spring-k8s"; - KubernetesDiscoveryProperties properties = KubernetesDiscoveryProperties.DEFAULT; - Service service = new ServiceBuilder().build(); - - Fabric8ServicePortData portData = Fabric8KubernetesDiscoveryClientUtils.endpointsPort(endpointSubset, serviceId, - properties, service); - Assertions.assertEquals(portData.portNumber(), 8080); - Assertions.assertEquals(portData.portName(), "http"); - Assertions.assertTrue(output.getOut().contains("endpoint ports has a single entry, using port : 8080")); - } - - /** - *
-	 *     - primary-port-name is null.
-	 * 
- */ - @Test - void testEndpointsPortNullPrimaryPortName(CapturedOutput output) { - EndpointSubset endpointSubset = new EndpointSubsetBuilder() - .withPorts(new EndpointPortBuilder().withPort(8080).build(), - new EndpointPortBuilder().withPort(8081).build()) - .build(); - String serviceId = "spring-k8s"; - KubernetesDiscoveryProperties properties = KubernetesDiscoveryProperties.DEFAULT; - Service service = new ServiceBuilder().withMetadata(new ObjectMeta()).build(); - - Fabric8ServicePortData portData = Fabric8KubernetesDiscoveryClientUtils.endpointsPort(endpointSubset, serviceId, - properties, service); - Assertions.assertEquals(portData.portNumber(), 8080); - Assertions.assertNull(portData.portName()); - Assertions.assertTrue(output.getOut().contains( - "did not find a primary-port-name in neither properties nor service labels for service with ID : spring-k8s")); - Assertions.assertTrue(output.getOut() - .contains("not found primary-port-name (with value: 'null') via properties or service labels")); - Assertions.assertTrue(output.getOut().contains("not found primary-port-name via 'https' to match port")); - Assertions.assertTrue(output.getOut().contains("not found primary-port-name via 'http' to match port")); - Assertions.assertTrue(output.getOut().contains(""" - Make sure that either the primary-port-name label has been added to the service, - or spring.cloud.kubernetes.discovery.primary-port-name has been configured. - Alternatively name the primary port 'https' or 'http' - An incorrect configuration may result in non-deterministic behaviour.""")); - } - - /** - *
-	 *     - primary-port-name is "three", such a port name does not exist.
-	 * 
- */ - @Test - void testEndpointsPortPrimaryPortNameIsPresentButNotFound(CapturedOutput output) { - EndpointSubset endpointSubset = new EndpointSubsetBuilder() - .withPorts(new EndpointPortBuilder().withPort(8080).withName("one").build(), - new EndpointPortBuilder().withPort(8081).withName("two").build()) - .build(); - String serviceId = "spring-k8s"; - - String primaryPortName = "three"; - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - true, "", Set.of(), Map.of(), primaryPortName, null, 0, false); - - Service service = new ServiceBuilder().withMetadata(new ObjectMeta()).build(); - - Fabric8ServicePortData portData = Fabric8KubernetesDiscoveryClientUtils.endpointsPort(endpointSubset, serviceId, - properties, service); - Assertions.assertEquals(portData.portNumber(), 8080); - Assertions.assertEquals(portData.portName(), "one"); - Assertions.assertTrue( - output.getOut().contains("will use primaryPortName : three for service with ID = spring-k8s")); - Assertions.assertTrue(output.getOut() - .contains("not found primary-port-name (with value: 'three') via properties or service labels")); - Assertions.assertTrue(output.getOut().contains("not found primary-port-name via 'https' to match port")); - Assertions.assertTrue(output.getOut().contains("not found primary-port-name via 'http' to match port")); - Assertions.assertTrue(output.getOut().contains(""" - Make sure that either the primary-port-name label has been added to the service, - or spring.cloud.kubernetes.discovery.primary-port-name has been configured. - Alternatively name the primary port 'https' or 'http' - An incorrect configuration may result in non-deterministic behaviour.""")); - } - - /** - *
-	 *     - primary-port-name is "two", such a port name exists and matches 8081
-	 * 
- */ - @Test - void testEndpointsPortPrimaryPortNameFound(CapturedOutput output) { - EndpointSubset endpointSubset = new EndpointSubsetBuilder() - .withPorts(new EndpointPortBuilder().withPort(8080).withName("one").build(), - new EndpointPortBuilder().withPort(8081).withName("two").build()) - .build(); - String serviceId = "spring-k8s"; - - String primaryPortName = "two"; - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - true, "", Set.of(), Map.of(), primaryPortName, null, 0, false); - - Service service = new ServiceBuilder().withMetadata(new ObjectMeta()).build(); - - Fabric8ServicePortData portData = Fabric8KubernetesDiscoveryClientUtils.endpointsPort(endpointSubset, serviceId, - properties, service); - Assertions.assertEquals(portData.portNumber(), 8081); - Assertions.assertEquals(portData.portName(), "two"); - Assertions.assertTrue( - output.getOut().contains("will use primaryPortName : two for service with ID = spring-k8s")); - Assertions.assertTrue(output.getOut().contains( - "found primary-port-name (with value: 'two') via properties or service labels to match port : 8081")); - } - - /** - *
-	 *     - primary-port-name is "three", such a port name does not exist.
-	 *     - https port exists and this one is returned
-	 * 
- */ - @Test - void testEndpointsPortPrimaryPortHttps(CapturedOutput output) { - EndpointSubset endpointSubset = new EndpointSubsetBuilder() - .withPorts(new EndpointPortBuilder().withPort(8080).withName("one").build(), - new EndpointPortBuilder().withPort(8081).withName("two").build(), - new EndpointPortBuilder().withPort(8082).withName("https").build()) - .build(); - String serviceId = "spring-k8s"; - - String primaryPortName = "three"; - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - true, "", Set.of(), Map.of(), primaryPortName, null, 0, false, false); - - Service service = new ServiceBuilder().withMetadata(new ObjectMeta()).build(); - - Fabric8ServicePortData portData = Fabric8KubernetesDiscoveryClientUtils.endpointsPort(endpointSubset, serviceId, - properties, service); - Assertions.assertEquals(portData.portNumber(), 8082); - Assertions.assertEquals(portData.portName(), "https"); - Assertions.assertTrue( - output.getOut().contains("will use primaryPortName : three for service with ID = spring-k8s")); - Assertions.assertTrue(output.getOut().contains( - "not found primary-port-name (with value: 'three') via properties or service labels to match port")); - Assertions.assertTrue(output.getOut().contains("found primary-port-name via 'https' to match port : 8082")); - } - - /** - *
-	 *     - primary-port-name is "three", such a port name does not exist.
-	 *     - http port exists and this one is returned
-	 * 
- */ - @Test - void testEndpointsPortPrimaryPortHttp(CapturedOutput output) { - EndpointSubset endpointSubset = new EndpointSubsetBuilder() - .withPorts(new EndpointPortBuilder().withPort(8080).withName("one").build(), - new EndpointPortBuilder().withPort(8081).withName("two").build(), - new EndpointPortBuilder().withPort(8082).withName("http").build()) - .build(); - String serviceId = "spring-k8s"; - - String primaryPortName = "three"; - KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, - true, "", Set.of(), Map.of(), primaryPortName, null, 0, false); - - Service service = new ServiceBuilder().withMetadata(new ObjectMeta()).build(); - - Fabric8ServicePortData portData = Fabric8KubernetesDiscoveryClientUtils.endpointsPort(endpointSubset, serviceId, - properties, service); - Assertions.assertEquals(portData.portNumber(), 8082); - Assertions.assertEquals(portData.portName(), "http"); - Assertions.assertTrue( - output.getOut().contains("will use primaryPortName : three for service with ID = spring-k8s")); - Assertions.assertTrue(output.getOut().contains( - "not found primary-port-name (with value: 'three') via properties or service labels to match port")); - Assertions.assertTrue(output.getOut().contains("found primary-port-name via 'http' to match port : 8082")); - } - /** *
 	 *      - ready addresses are empty
@@ -442,7 +158,7 @@ class KubernetesDiscoveryClientUtilsTests {
 		EndpointAddress address = new EndpointAddressBuilder().withNewTargetRef().withUid("123").endTargetRef()
 				.withIp("127.0.0.1").build();
 
-		Fabric8ServicePortData portData = new Fabric8ServicePortData(8080, "http");
+		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);
@@ -465,7 +181,7 @@ class KubernetesDiscoveryClientUtilsTests {
 				.withSpec(new ServiceSpecBuilder().withExternalName("spring.io").withType("ExternalName").build())
 				.withMetadata(new ObjectMetaBuilder().withUid("123").build()).build();
 
-		Fabric8ServicePortData portData = new Fabric8ServicePortData(-1, "http");
+		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);
@@ -489,7 +205,7 @@ class KubernetesDiscoveryClientUtilsTests {
 
 		EndpointAddress endpointAddress = new EndpointAddressBuilder().withIp("127.0.0.1").build();
 
-		Fabric8ServicePortData portData = new Fabric8ServicePortData(0, "http");
+		ServicePortNameAndNumber portData = new ServicePortNameAndNumber(0, "http");
 		ServiceInstance serviceInstance = Fabric8KubernetesDiscoveryClientUtils.serviceInstance(null, service,
 				endpointAddress, portData, "my-service", Map.of("a", "b"), "k8s", KubernetesDiscoveryProperties.DEFAULT,
 				null);
@@ -507,4 +223,42 @@ class KubernetesDiscoveryClientUtilsTests {
 		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);
+	}
+
 }
diff --git a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/ServicePortSecureResolverTest.java b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/ServicePortSecureResolverTest.java
index 64c9a56e..5d2f65e1 100644
--- a/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/ServicePortSecureResolverTest.java
+++ b/spring-cloud-kubernetes-fabric8-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/discovery/ServicePortSecureResolverTest.java
@@ -26,6 +26,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.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
+import org.springframework.cloud.kubernetes.commons.discovery.ServicePortNameAndNumber;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.springframework.cloud.kubernetes.fabric8.discovery.ServicePortSecureResolver.Input;
@@ -42,16 +43,16 @@ class ServicePortSecureResolverTest {
 	private static final Map SECURED_ON_MAP = Collections.singletonMap("secured", "on");
 
 	private static final ServicePortSecureResolver.Input SECURED_TRUE = new ServicePortSecureResolver.Input(
-			new Fabric8ServicePortData(8080, "http"), "dummy", SECURED_TRUE_MAP, Collections.emptyMap());
+			new ServicePortNameAndNumber(8080, "http"), "dummy", SECURED_TRUE_MAP, Collections.emptyMap());
 
 	private static final ServicePortSecureResolver.Input SECURED_1 = new ServicePortSecureResolver.Input(
-			new Fabric8ServicePortData(1234, "http"), "dummy", SECURED_1_MAP, Collections.emptyMap());
+			new ServicePortNameAndNumber(1234, "http"), "dummy", SECURED_1_MAP, Collections.emptyMap());
 
 	private static final ServicePortSecureResolver.Input SECURED_YES = new ServicePortSecureResolver.Input(
-			new Fabric8ServicePortData(4321, "http"), "dummy", SECURED_YES_MAP, Collections.emptyMap());
+			new ServicePortNameAndNumber(4321, "http"), "dummy", SECURED_YES_MAP, Collections.emptyMap());
 
 	private static final ServicePortSecureResolver.Input SECURED_ON = new ServicePortSecureResolver.Input(
-			new Fabric8ServicePortData(4321, "http"), "dummy", SECURED_ON_MAP, Collections.emptyMap());
+			new ServicePortNameAndNumber(4321, "http"), "dummy", SECURED_ON_MAP, Collections.emptyMap());
 
 	@Test
 	void testPortNumbersOnly() {
@@ -61,20 +62,18 @@ class ServicePortSecureResolverTest {
 
 		ServicePortSecureResolver secureResolver = new ServicePortSecureResolver(properties);
 
-		assertThat(
-				secureResolver.resolve(new Input(new Fabric8ServicePortData(-1, "http"), "dummy", Map.of(), Map.of())))
-						.isFalse();
 		assertThat(secureResolver
-				.resolve(new Input(new Fabric8ServicePortData(8080, "http"), "dummy", Map.of(), Map.of()))).isFalse();
+				.resolve(new Input(new ServicePortNameAndNumber(-1, "http"), "dummy", Map.of(), Map.of()))).isFalse();
 		assertThat(secureResolver
-				.resolve(new Input(new Fabric8ServicePortData(1234, "http"), "dummy", Map.of(), Map.of()))).isFalse();
-		assertThat(
-				secureResolver.resolve(new Input(new Fabric8ServicePortData(443, "http"), "dummy", Map.of(), Map.of())))
-						.isTrue();
+				.resolve(new Input(new ServicePortNameAndNumber(8080, "http"), "dummy", Map.of(), Map.of()))).isFalse();
 		assertThat(secureResolver
-				.resolve(new Input(new Fabric8ServicePortData(8443, "http"), "dummy", Map.of(), Map.of()))).isTrue();
+				.resolve(new Input(new ServicePortNameAndNumber(1234, "http"), "dummy", Map.of(), Map.of()))).isFalse();
 		assertThat(secureResolver
-				.resolve(new Input(new Fabric8ServicePortData(12345, "http"), "dummy", Map.of(), Map.of()))).isTrue();
+				.resolve(new Input(new ServicePortNameAndNumber(443, "http"), "dummy", Map.of(), Map.of()))).isTrue();
+		assertThat(secureResolver
+				.resolve(new Input(new ServicePortNameAndNumber(8443, "http"), "dummy", Map.of(), Map.of()))).isTrue();
+		assertThat(secureResolver
+				.resolve(new Input(new ServicePortNameAndNumber(12345, "http"), "dummy", Map.of(), Map.of()))).isTrue();
 	}
 
 	@Test
@@ -93,7 +92,7 @@ class ServicePortSecureResolverTest {
 				false, null, Set.of(443, 8443, 12345), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT,
 				0, true);
 		ServicePortSecureResolver secureResolver = new ServicePortSecureResolver(properties);
-		Fabric8ServicePortData portData = new Fabric8ServicePortData(8080, "http");
+		ServicePortNameAndNumber portData = new ServicePortNameAndNumber(8080, "http");
 		Input input = new Input(portData, "dummy", Map.of(), Map.of());
 
 		boolean result = secureResolver.resolve(input);
@@ -107,7 +106,7 @@ class ServicePortSecureResolverTest {
 				false, null, Set.of(443, 8443), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0,
 				true);
 		ServicePortSecureResolver secureResolver = new ServicePortSecureResolver(properties);
-		Fabric8ServicePortData portData = new Fabric8ServicePortData(8080, "http");
+		ServicePortNameAndNumber portData = new ServicePortNameAndNumber(8080, "http");
 		Input input = new Input(portData, "dummy", Map.of("secured", "right"), Map.of());
 
 		boolean result = secureResolver.resolve(input);
@@ -121,7 +120,7 @@ class ServicePortSecureResolverTest {
 				false, null, Set.of(443, 8443), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0,
 				true);
 		ServicePortSecureResolver secureResolver = new ServicePortSecureResolver(properties);
-		Fabric8ServicePortData portData = new Fabric8ServicePortData(8080, "http");
+		ServicePortNameAndNumber portData = new ServicePortNameAndNumber(8080, "http");
 		Input input = new Input(portData, "dummy", Map.of("secured", "true"), Map.of());
 
 		boolean result = secureResolver.resolve(input);
@@ -136,7 +135,7 @@ class ServicePortSecureResolverTest {
 				false, null, Set.of(443, 8443), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0,
 				true);
 		ServicePortSecureResolver secureResolver = new ServicePortSecureResolver(properties);
-		Fabric8ServicePortData portData = new Fabric8ServicePortData(8080, "http");
+		ServicePortNameAndNumber portData = new ServicePortNameAndNumber(8080, "http");
 		Input input = new Input(portData, "dummy", Map.of(), Map.of("secured", "right"));
 
 		boolean result = secureResolver.resolve(input);
@@ -150,7 +149,7 @@ class ServicePortSecureResolverTest {
 				false, null, Set.of(443, 8443), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0,
 				true);
 		ServicePortSecureResolver secureResolver = new ServicePortSecureResolver(properties);
-		Fabric8ServicePortData portData = new Fabric8ServicePortData(8080, "http");
+		ServicePortNameAndNumber portData = new ServicePortNameAndNumber(8080, "http");
 		Input input = new Input(portData, "dummy", Map.of(), Map.of("secured", "true"));
 
 		boolean result = secureResolver.resolve(input);
@@ -164,7 +163,7 @@ class ServicePortSecureResolverTest {
 		KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
 				false, null, Set.of(8080), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, true);
 		ServicePortSecureResolver secureResolver = new ServicePortSecureResolver(properties);
-		Fabric8ServicePortData portData = new Fabric8ServicePortData(8080, "http");
+		ServicePortNameAndNumber portData = new ServicePortNameAndNumber(8080, "http");
 		Input input = new Input(portData, "dummy", Map.of(), Map.of());
 
 		boolean result = secureResolver.resolve(input);
@@ -178,7 +177,7 @@ class ServicePortSecureResolverTest {
 		KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
 				false, null, Set.of(8081), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, true);
 		ServicePortSecureResolver secureResolver = new ServicePortSecureResolver(properties);
-		Fabric8ServicePortData portData = new Fabric8ServicePortData(8080, "https");
+		ServicePortNameAndNumber portData = new ServicePortNameAndNumber(8080, "https");
 		Input input = new Input(portData, "dummy", Map.of(), Map.of());
 
 		boolean result = secureResolver.resolve(input);