diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientUtils.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientUtils.java new file mode 100644 index 00000000..b5bc404c --- /dev/null +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientUtils.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.discovery; + +import java.util.Map; +import java.util.Optional; + +import io.kubernetes.client.openapi.models.V1ObjectMeta; +import io.kubernetes.client.openapi.models.V1Service; +import org.apache.commons.logging.LogFactory; + +import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties; +import org.springframework.core.log.LogAccessor; + +/** + * @author wind57 + */ +final class KubernetesDiscoveryClientUtils { + + private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(KubernetesDiscoveryClientUtils.class)); + + private KubernetesDiscoveryClientUtils() { + + } + + static boolean matchesServiceLabels(V1Service service, KubernetesDiscoveryProperties properties) { + + Map propertiesServiceLabels = properties.serviceLabels(); + Map serviceLabels = Optional.ofNullable(service.getMetadata()).map(V1ObjectMeta::getLabels) + .orElse(Map.of()); + + if (propertiesServiceLabels.isEmpty()) { + LOG.debug(() -> "service labels from properties are empty, service with name : '" + + service.getMetadata().getName() + "' will match"); + return true; + } + + if (serviceLabels.isEmpty()) { + LOG.debug(() -> "service with name : '" + service.getMetadata().getName() + "' does not have labels"); + return false; + } + + LOG.debug(() -> "Service labels from properties : " + properties.serviceLabels()); + LOG.debug(() -> "Service labels from service : " + service.getMetadata().getLabels()); + + return serviceLabels.keySet().containsAll(propertiesServiceLabels.keySet()); + + } + +} diff --git a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClient.java b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClient.java index fd9a4db5..cb5b0458 100644 --- a/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClient.java +++ b/spring-cloud-kubernetes-client-discovery/src/main/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClient.java @@ -46,6 +46,7 @@ import org.springframework.core.log.LogAccessor; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import static org.springframework.cloud.kubernetes.client.discovery.KubernetesDiscoveryClientUtils.matchesServiceLabels; 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.UNSET_PORT_NAME; @@ -105,7 +106,7 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient { List services = properties.allNamespaces() ? serviceLister.list().stream().filter(svc -> serviceId.equals(svc.getMetadata().getName())).toList() : List.of(serviceLister.namespace(namespace).get(serviceId)); - if (services.size() == 0 || !services.stream().anyMatch(this::matchServiceLabels)) { + if (services.size() == 0 || !services.stream().anyMatch(service -> matchesServiceLabels(service, properties))) { // no such service present in the cluster return new ArrayList<>(); } @@ -229,8 +230,8 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient { public List getServices() { List services = properties.allNamespaces() ? serviceLister.list() : serviceLister.namespace(namespace).list(); - return services.stream().filter(this::matchServiceLabels).map(s -> s.getMetadata().getName()) - .collect(Collectors.toList()); + return services.stream().filter(service -> matchesServiceLabels(service, properties)) + .map(s -> s.getMetadata().getName()).collect(Collectors.toList()); } @PostConstruct @@ -253,26 +254,4 @@ public class KubernetesInformerDiscoveryClient implements DiscoveryClient { + " services) , discovery client is now available"); } - private boolean matchServiceLabels(V1Service service) { - LOG.debug(() -> "Kubernetes Service Label Properties:"); - if (properties.serviceLabels() != null) { - properties.serviceLabels().forEach((key, value) -> LOG.debug(() -> key + ":" + value)); - } - LOG.debug(() -> "Service " + service.getMetadata().getName() + " labels:"); - if (service.getMetadata() != null && service.getMetadata().getLabels() != null) { - service.getMetadata().getLabels().forEach((key, value) -> LOG.debug(() -> key + ":" + value)); - } - // safeguard - if (service.getMetadata() == null) { - return false; - } - if (properties.serviceLabels() == null || properties.serviceLabels().isEmpty()) { - return true; - } - return properties.serviceLabels().keySet().stream() - .allMatch(k -> service.getMetadata().getLabels() != null - && service.getMetadata().getLabels().containsKey(k) - && service.getMetadata().getLabels().get(k).equals(properties.serviceLabels().get(k))); - } - } diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientUtilsTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientUtilsTests.java new file mode 100644 index 00000000..4f3551e6 --- /dev/null +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesDiscoveryClientUtilsTests.java @@ -0,0 +1,170 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.discovery; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import io.kubernetes.client.openapi.models.V1ObjectMeta; +import io.kubernetes.client.openapi.models.V1Service; +import io.kubernetes.client.openapi.models.V1ServiceBuilder; +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.client.discovery.KubernetesDiscoveryClientUtils.matchesServiceLabels; + +/** + * @author wind57 + */ +@ExtendWith(OutputCaptureExtension.class) +class KubernetesDiscoveryClientUtilsTests { + + /** + * properties service labels are empty + */ + @Test + void testEmptyServiceLabelsFromProperties(CapturedOutput output) { + KubernetesDiscoveryProperties properties = KubernetesDiscoveryProperties.DEFAULT; + V1Service service = new V1ServiceBuilder().withMetadata(new V1ObjectMeta().name("my-service")).build(); + + boolean result = matchesServiceLabels(service, properties); + Assertions.assertTrue(result); + Assertions.assertTrue(output.getOut() + .contains("service labels from properties are empty, service with name : 'my-service' will match")); + } + + /** + * labels from service are empty + */ + @Test + void testEmptyServiceLabelsFromService(CapturedOutput output) { + Map propertiesLabels = Map.of("key", "value"); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + true, "", Set.of(), propertiesLabels, "", null, 0, false); + V1Service service = new V1ServiceBuilder().withMetadata(new V1ObjectMeta().name("my-service")).build(); + + boolean result = matchesServiceLabels(service, properties); + Assertions.assertFalse(result); + Assertions.assertTrue(output.getOut().contains("service with name : 'my-service' does not have labels")); + } + + /** + *
+	 *     properties = [a=b]
+	 *     service    = [a=b]
+	 *
+	 *     This means the service is picked-up.
+	 * 
+ */ + @Test + void testOne(CapturedOutput output) { + Map propertiesLabels = Map.of("a", "b"); + Map serviceLabels = Map.of("a", "b"); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + true, "", Set.of(), propertiesLabels, "", null, 0, false); + V1Service service = new V1ServiceBuilder() + .withMetadata(new V1ObjectMeta().labels(serviceLabels).name("my-service")).build(); + + boolean result = matchesServiceLabels(service, properties); + Assertions.assertTrue(result); + Assertions.assertTrue(output.getOut().contains("Service labels from properties : {a=b}")); + Assertions.assertTrue(output.getOut().contains("Service labels from service : {a=b}")); + } + + /** + *
+	 *     properties = [a=b, c=d]
+	 *     service    = [a=b]
+	 *
+	 *     This means the service is not picked-up.
+	 * 
+ */ + @Test + void testTwo(CapturedOutput output) { + Map propertiesLabels = ordered(Map.of("a", "b", "c", "d")); + Map serviceLabels = Map.of("a", "b"); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + true, "", Set.of(), propertiesLabels, "", null, 0, false); + V1Service service = new V1ServiceBuilder() + .withMetadata(new V1ObjectMeta().labels(serviceLabels).name("my-service")).build(); + + boolean result = matchesServiceLabels(service, properties); + Assertions.assertFalse(result); + Assertions.assertTrue(output.getOut().contains("Service labels from properties : {a=b, c=d}")); + Assertions.assertTrue(output.getOut().contains("Service labels from service : {a=b}")); + } + + /** + *
+	 *     properties = [a=b, c=d]
+	 *     service    = [a=b, c=d]
+	 *
+	 *     This means the service is picked-up.
+	 * 
+ */ + @Test + void testThree(CapturedOutput output) { + Map propertiesLabels = ordered(Map.of("a", "b", "c", "d")); + Map serviceLabels = ordered(Map.of("a", "b", "c", "d")); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + true, "", Set.of(), propertiesLabels, "", null, 0, false); + V1Service service = new V1ServiceBuilder() + .withMetadata(new V1ObjectMeta().labels(serviceLabels).name("my-service")).build(); + + boolean result = matchesServiceLabels(service, properties); + Assertions.assertTrue(result); + Assertions.assertTrue(output.getOut().contains("Service labels from properties : {a=b, c=d}")); + Assertions.assertTrue(output.getOut().contains("Service labels from service : {a=b, c=d}")); + } + + /** + *
+	 *     properties = [a=b]
+	 *     service    = [a=b, c=d]
+	 *
+	 *     This means the service is picked-up.
+	 * 
+ */ + @Test + void testFour(CapturedOutput output) { + Map propertiesLabels = Map.of("a", "b"); + Map serviceLabels = ordered(Map.of("a", "b", "c", "d")); + KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60L, + true, "", Set.of(), propertiesLabels, "", null, 0, false); + V1Service service = new V1ServiceBuilder() + .withMetadata(new V1ObjectMeta().labels(serviceLabels).name("my-service")).build(); + + boolean result = matchesServiceLabels(service, properties); + Assertions.assertTrue(result); + Assertions.assertTrue(output.getOut().contains("Service labels from properties : {a=b}")); + Assertions.assertTrue(output.getOut().contains("Service labels from service : {a=b, c=d}")); + } + + // preserve order for testing reasons + private Map ordered(Map input) { + return input.entrySet().stream().sorted(Map.Entry.comparingByKey()).collect( + Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (left, right) -> left, LinkedHashMap::new)); + } + +} diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientTests.java index 6744a199..0b069f42 100644 --- a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientTests.java +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/KubernetesInformerDiscoveryClientTests.java @@ -235,7 +235,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpoints1); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); + Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -250,7 +250,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpoints1); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); + Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -277,7 +277,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpointWithoutReadyAddresses); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, true, null, Set.of(), null, null, null, 0, true); + Set.of(), true, 60, true, null, Set.of(), Map.of(), null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -318,7 +318,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); + Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -337,7 +337,7 @@ public class KubernetesInformerDiscoveryClientTests { testEndpointWithMultiplePortsWithoutSupportedPortNames); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); + Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -353,7 +353,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, "https", null, 0, true); + Set.of(), true, 60, false, null, Set.of(), Map.of(), "https", null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -369,7 +369,7 @@ public class KubernetesInformerDiscoveryClientTests { testEndpointWithMultiplePortsWithoutSupportedPortNames); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, "oops", null, 0, true); + Set.of(), true, 60, false, null, Set.of(), Map.of(), "oops", null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -384,7 +384,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpointWithMultiplePorts); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); + Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -399,7 +399,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpointWithMultiplePortsWithoutHttps); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); + Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -415,7 +415,7 @@ public class KubernetesInformerDiscoveryClientTests { testEndpointWithMultiplePortsWithoutSupportedPortNames); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, true); + Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, true); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient("namespace1", sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); @@ -430,7 +430,7 @@ public class KubernetesInformerDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpoints1, testEndpoints2); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, false); + Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, false); KubernetesInformerDiscoveryClient discoveryClient = new KubernetesInformerDiscoveryClient(null, sharedInformerFactory, serviceLister, endpointsLister, null, null, kubernetesDiscoveryProperties); diff --git a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientTests.java b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientTests.java index 7d76116c..4f6a98ef 100644 --- a/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientTests.java +++ b/spring-cloud-kubernetes-client-discovery/src/test/java/org/springframework/cloud/kubernetes/client/discovery/reactive/KubernetesInformerReactiveDiscoveryClientTests.java @@ -17,6 +17,7 @@ package org.springframework.cloud.kubernetes.client.discovery.reactive; import java.util.HashMap; +import java.util.Map; import java.util.Set; import io.kubernetes.client.informer.SharedInformerFactory; @@ -101,7 +102,7 @@ public class KubernetesInformerReactiveDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpoints1); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, true, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, false); + Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, false); KubernetesInformerReactiveDiscoveryClient discoveryClient = new KubernetesInformerReactiveDiscoveryClient( new KubernetesNamespaceProvider(new MockEnvironment()), sharedInformerFactory, serviceLister, @@ -120,7 +121,7 @@ public class KubernetesInformerReactiveDiscoveryClientTests { Lister endpointsLister = setupEndpointsLister(testEndpoints1); KubernetesDiscoveryProperties kubernetesDiscoveryProperties = new KubernetesDiscoveryProperties(true, false, - Set.of(), true, 60, false, null, Set.of(), null, null, null, 0, false); + Set.of(), true, 60, false, null, Set.of(), Map.of(), null, null, 0, false); KubernetesNamespaceProvider kubernetesNamespaceProvider = mock(KubernetesNamespaceProvider.class); when(kubernetesNamespaceProvider.getNamespace()).thenReturn("namespace1"); diff --git a/spring-cloud-kubernetes-client-discovery/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-client-discovery/src/test/resources/logback-test.xml index ee243343..cd755579 100644 --- a/spring-cloud-kubernetes-client-discovery/src/test/resources/logback-test.xml +++ b/spring-cloud-kubernetes-client-discovery/src/test/resources/logback-test.xml @@ -11,5 +11,8 @@ - + + + +