From 694f89add9d4344514fa4d3702624280d2be2e21 Mon Sep 17 00:00:00 2001 From: piomin Date: Fri, 3 Jul 2020 15:23:05 +0200 Subject: [PATCH] #562 Adding all namespaces handling + tests --- spring-cloud-kubernetes-loadbalancer/pom.xml | 5 + .../KubernetesClientConfiguration.java | 7 +- .../KubernetesServiceInstance.java | 3 + .../KubernetesServicesListSupplier.java | 29 +++-- .../KubernetesServiceInstanceMapperTest.java | 67 ++++------- .../KubernetesServiceListSupplierTest.java | 113 ++++++++++++++++++ 6 files changed, 171 insertions(+), 53 deletions(-) create mode 100644 spring-cloud-kubernetes-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServiceListSupplierTest.java diff --git a/spring-cloud-kubernetes-loadbalancer/pom.xml b/spring-cloud-kubernetes-loadbalancer/pom.xml index 42515da1..d564c118 100644 --- a/spring-cloud-kubernetes-loadbalancer/pom.xml +++ b/spring-cloud-kubernetes-loadbalancer/pom.xml @@ -41,5 +41,10 @@ spring-boot-starter-test test + + io.fabric8 + kubernetes-server-mock + test + diff --git a/spring-cloud-kubernetes-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesClientConfiguration.java b/spring-cloud-kubernetes-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesClientConfiguration.java index dddc13f4..c16c9fe8 100644 --- a/spring-cloud-kubernetes-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesClientConfiguration.java +++ b/spring-cloud-kubernetes-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesClientConfiguration.java @@ -19,6 +19,7 @@ package org.springframework.cloud.kubernetes.loadbalancer; import io.fabric8.kubernetes.client.KubernetesClient; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; @@ -33,8 +34,10 @@ public class KubernetesClientConfiguration { @ConditionalOnProperty(name = "spring.cloud.kubernetes.loadbalancer.mode", havingValue = "SERVICE") KubernetesServicesListSupplier kubernetesServicesListSupplier(Environment environment, - KubernetesClient kubernetesClient, KubernetesServiceInstanceMapper mapper) { - return new KubernetesServicesListSupplier(environment, kubernetesClient, mapper); + KubernetesClient kubernetesClient, KubernetesServiceInstanceMapper mapper, + KubernetesDiscoveryProperties discoveryProperties) { + return new KubernetesServicesListSupplier(environment, kubernetesClient, mapper, + discoveryProperties); } } diff --git a/spring-cloud-kubernetes-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServiceInstance.java b/spring-cloud-kubernetes-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServiceInstance.java index f1315277..8967e072 100644 --- a/spring-cloud-kubernetes-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServiceInstance.java +++ b/spring-cloud-kubernetes-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServiceInstance.java @@ -40,6 +40,9 @@ public class KubernetesServiceInstance implements ServiceInstance { private Map metadata; + KubernetesServiceInstance() { + } + KubernetesServiceInstance(String serviceId, String instanceId, int port, boolean secure, String host, URI uri, Map metadata) { this.serviceId = serviceId; diff --git a/spring-cloud-kubernetes-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServicesListSupplier.java b/spring-cloud-kubernetes-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServicesListSupplier.java index 51fe8fbc..29d61bfa 100644 --- a/spring-cloud-kubernetes-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServicesListSupplier.java +++ b/spring-cloud-kubernetes-loadbalancer/src/main/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServicesListSupplier.java @@ -25,6 +25,7 @@ import org.apache.commons.lang.StringUtils; import reactor.core.publisher.Flux; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.core.env.Environment; @@ -38,12 +39,16 @@ public class KubernetesServicesListSupplier implements ServiceInstanceListSuppli private KubernetesClient kubernetesClient; + private KubernetesDiscoveryProperties discoveryProperties; + private KubernetesServiceInstanceMapper mapper; KubernetesServicesListSupplier(Environment environment, - KubernetesClient kubernetesClient, KubernetesServiceInstanceMapper mapper) { + KubernetesClient kubernetesClient, KubernetesServiceInstanceMapper mapper, + KubernetesDiscoveryProperties discoveryProperties) { this.environment = environment; this.kubernetesClient = kubernetesClient; + this.discoveryProperties = discoveryProperties; this.mapper = mapper; } @@ -55,13 +60,21 @@ public class KubernetesServicesListSupplier implements ServiceInstanceListSuppli @Override public Flux> get() { List result = new ArrayList<>(); - Service service = StringUtils.isNotBlank(this.kubernetesClient.getNamespace()) - ? this.kubernetesClient - .services().inNamespace(this.kubernetesClient.getNamespace()) - .withName(this.getServiceId()).get() - : this.kubernetesClient.services().withName(this.getServiceId()).get(); - if (service != null) { - result.add(mapper.map(service)); + if (discoveryProperties.isAllNamespaces()) { + List services = this.kubernetesClient.services().inAnyNamespace() + .withField("metadata.name", this.getServiceId()).list().getItems(); + services.forEach(service -> result.add(mapper.map(service))); + } + else { + Service service = StringUtils.isNotBlank(this.kubernetesClient.getNamespace()) + ? this.kubernetesClient.services() + .inNamespace(this.kubernetesClient.getNamespace()) + .withName(this.getServiceId()).get() + : this.kubernetesClient.services().withName(this.getServiceId()) + .get(); + if (service != null) { + result.add(mapper.map(service)); + } } return Flux.just(result); } diff --git a/spring-cloud-kubernetes-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServiceInstanceMapperTest.java b/spring-cloud-kubernetes-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServiceInstanceMapperTest.java index a696fcfa..2d5d5fc2 100644 --- a/spring-cloud-kubernetes-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServiceInstanceMapperTest.java +++ b/spring-cloud-kubernetes-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServiceInstanceMapperTest.java @@ -34,13 +34,12 @@ import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryPropert public class KubernetesServiceInstanceMapperTest { @Test - void testMapperSimple() { + public void testMapperSimple() { KubernetesLoadBalancerProperties properties = new KubernetesLoadBalancerProperties(); - KubernetesDiscoveryProperties discoveryProperties = - new KubernetesDiscoveryProperties(); + KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(); Service service = buildService("test", "abc", 8080, null, new HashMap<>()); - KubernetesServiceInstance instance = - new KubernetesServiceInstanceMapper(properties, discoveryProperties).map(service); + KubernetesServiceInstance instance = new KubernetesServiceInstanceMapper( + properties, discoveryProperties).map(service); Assertions.assertNotNull(instance); Assertions.assertEquals("test", instance.getServiceId()); Assertions.assertEquals("abc", instance.getInstanceId()); @@ -50,20 +49,13 @@ public class KubernetesServiceInstanceMapperTest { void testMapperMultiplePorts() { KubernetesLoadBalancerProperties properties = new KubernetesLoadBalancerProperties(); properties.setPortName("http"); - KubernetesDiscoveryProperties discoveryProperties = - new KubernetesDiscoveryProperties(); + KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(); List ports = new ArrayList<>(); - ports.add(new ServicePortBuilder() - .withPort(8080) - .withName("web") - .build()); - ports.add(new ServicePortBuilder() - .withPort(9000) - .withName("http") - .build()); + ports.add(new ServicePortBuilder().withPort(8080).withName("web").build()); + ports.add(new ServicePortBuilder().withPort(9000).withName("http").build()); Service service = buildService("test", "abc", ports, new HashMap<>()); - KubernetesServiceInstance instance = - new KubernetesServiceInstanceMapper(properties, discoveryProperties).map(service); + KubernetesServiceInstance instance = new KubernetesServiceInstanceMapper( + properties, discoveryProperties).map(service); Assertions.assertNotNull(instance); Assertions.assertEquals("test", instance.getServiceId()); Assertions.assertEquals("abc", instance.getInstanceId()); @@ -73,11 +65,10 @@ public class KubernetesServiceInstanceMapperTest { @Test void testMapperSecure() { KubernetesLoadBalancerProperties properties = new KubernetesLoadBalancerProperties(); - KubernetesDiscoveryProperties discoveryProperties = - new KubernetesDiscoveryProperties(); + KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(); Service service = buildService("test", "abc", 443, null, new HashMap<>()); - KubernetesServiceInstance instance = - new KubernetesServiceInstanceMapper(properties, discoveryProperties).map(service); + KubernetesServiceInstance instance = new KubernetesServiceInstanceMapper( + properties, discoveryProperties).map(service); Assertions.assertNotNull(instance); Assertions.assertEquals("test", instance.getServiceId()); Assertions.assertEquals("abc", instance.getInstanceId()); @@ -87,14 +78,13 @@ public class KubernetesServiceInstanceMapperTest { @Test void testMapperSecureWithLabels() { KubernetesLoadBalancerProperties properties = new KubernetesLoadBalancerProperties(); - KubernetesDiscoveryProperties discoveryProperties = - new KubernetesDiscoveryProperties(); + KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(); HashMap labels = new HashMap<>(); labels.put("secured", "true"); labels.put("label1", "123"); Service service = buildService("test", "abc", 8080, null, labels); - KubernetesServiceInstance instance = - new KubernetesServiceInstanceMapper(properties, discoveryProperties).map(service); + KubernetesServiceInstance instance = new KubernetesServiceInstanceMapper( + properties, discoveryProperties).map(service); Assertions.assertNotNull(instance); Assertions.assertEquals("test", instance.getServiceId()); Assertions.assertEquals("abc", instance.getInstanceId()); @@ -102,26 +92,17 @@ public class KubernetesServiceInstanceMapperTest { Assertions.assertEquals(2, instance.getMetadata().keySet().size()); } - - private Service buildService(String name, String uid, List ports, Map labels) { - return new ServiceBuilder() - .withNewMetadata() - .withName(name) - .withNewUid(uid) - .addToLabels(labels) - .addToAnnotations(new HashMap<>(0)) - .endMetadata() - .withNewSpec() - .addAllToPorts(ports) - .endSpec() - .build(); + private Service buildService(String name, String uid, List ports, + Map labels) { + return new ServiceBuilder().withNewMetadata().withName(name).withNewUid(uid) + .addToLabels(labels).addToAnnotations(new HashMap<>(0)).endMetadata() + .withNewSpec().addAllToPorts(ports).endSpec().build(); } - private Service buildService(String name, String uid, int port, String portName, Map labels) { - ServicePort servicePort = new ServicePortBuilder() - .withPort(port) - .withName(portName) - .build(); + private Service buildService(String name, String uid, int port, String portName, + Map labels) { + ServicePort servicePort = new ServicePortBuilder().withPort(port) + .withName(portName).build(); return buildService(name, uid, Collections.singletonList(servicePort), labels); } diff --git a/spring-cloud-kubernetes-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServiceListSupplierTest.java b/spring-cloud-kubernetes-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServiceListSupplierTest.java new file mode 100644 index 00000000..542fe17a --- /dev/null +++ b/spring-cloud-kubernetes-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/loadbalancer/KubernetesServiceListSupplierTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2013-2020 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.loadbalancer; + +import java.util.List; + +import io.fabric8.kubernetes.api.model.DoneableService; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.ServiceBuilder; +import io.fabric8.kubernetes.api.model.ServiceList; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.Watch; +import io.fabric8.kubernetes.client.Watcher; +import io.fabric8.kubernetes.client.dsl.FilterWatchListMultiDeletable; +import io.fabric8.kubernetes.client.dsl.MixedOperation; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.ServiceResource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryProperties; +import org.springframework.core.env.Environment; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class KubernetesServiceListSupplierTest { + + @Mock + Environment environment; + + @Mock + KubernetesServiceInstanceMapper mapper; + + @Mock + KubernetesClient client; + + @Mock + MixedOperation> serviceOperation; + + @Mock + NonNamespaceOperation> namespaceOperation; + + @Mock + ServiceResource serviceResource; + + @Mock + FilterWatchListMultiDeletable> multiDeletable; + + @Test + void testPositiveMatch() { + when(environment.getProperty("loadbalancer.client.name")) + .thenReturn("test-service"); + when(mapper.map(any(Service.class))).thenReturn(new KubernetesServiceInstance()); + when(this.client.getNamespace()).thenReturn("test"); + when(this.client.services()).thenReturn(this.serviceOperation); + when(this.serviceOperation.inNamespace("test")).thenReturn(namespaceOperation); + when(this.namespaceOperation.withName("test-service")) + .thenReturn(this.serviceResource); + when(this.serviceResource.get()).thenReturn(buildService("test-service", 8080)); + KubernetesServicesListSupplier supplier = new KubernetesServicesListSupplier( + environment, client, mapper, new KubernetesDiscoveryProperties()); + List instances = supplier.get().blockFirst(); + assert instances != null; + Assertions.assertEquals(1, instances.size()); + } + + @Test + void testPositiveMatchAllNamespaces() { + when(environment.getProperty("loadbalancer.client.name")) + .thenReturn("test-service"); + when(mapper.map(any(Service.class))).thenReturn(new KubernetesServiceInstance()); + when(this.client.services()).thenReturn(this.serviceOperation); + when(this.serviceOperation.inAnyNamespace()).thenReturn(this.multiDeletable); + when(this.multiDeletable.withField("metadata.name", "test-service")) + .thenReturn(this.multiDeletable); + ServiceList serviceList = new ServiceList(); + serviceList.getItems().add(buildService("test-service", 8080)); + when(this.multiDeletable.list()).thenReturn(serviceList); + KubernetesDiscoveryProperties discoveryProperties = new KubernetesDiscoveryProperties(); + discoveryProperties.setAllNamespaces(true); + KubernetesServicesListSupplier supplier = new KubernetesServicesListSupplier( + environment, client, mapper, discoveryProperties); + List instances = supplier.get().blockFirst(); + assert instances != null; + Assertions.assertEquals(1, instances.size()); + } + + private Service buildService(String name, int port) { + return new ServiceBuilder().withNewMetadata().withName(name).endMetadata() + .withNewSpec().addNewPort().withPort(8080).endPort().endSpec().build(); + } + +}