From f0eab0e0b88457d8404297d4cb09e6bd5158fe58 Mon Sep 17 00:00:00 2001 From: wuzishu Date: Tue, 18 Jun 2019 22:58:38 +0800 Subject: [PATCH] Enhanced Ribbon can support access to workloads through services (#388) * Enhanced Ribbon can support access to workloads through services while being compatible with Istio traffic policies * update after checkstyle * update * add cluster_domian config * add tests for service mode * update doc for new feature and refactoring code structure --- README.adoc | 28 ++++ .../src/main/asciidoc/ribbon-integration.adoc | 26 ++++ spring-cloud-kubernetes-ribbon/pom.xml | 5 + .../ribbon/KubernetesEndpointsServerList.java | 95 ++++++++++++++ .../KubernetesRibbonClientConfiguration.java | 13 +- .../ribbon/KubernetesRibbonMode.java | 35 +++++ .../ribbon/KubernetesRibbonProperties.java | 91 +++++++++++++ .../ribbon/KubernetesServerList.java | 121 ++++++++++-------- .../ribbon/KubernetesServicesServerList.java | 92 +++++++++++++ .../ribbon/RibbonWithServiceModeTest.java | 108 ++++++++++++++++ 10 files changed, 558 insertions(+), 56 deletions(-) create mode 100644 spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesEndpointsServerList.java create mode 100644 spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonMode.java create mode 100644 spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonProperties.java create mode 100644 spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesServicesServerList.java create mode 100644 spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonWithServiceModeTest.java diff --git a/README.adoc b/README.adoc index aa34f217..b2301858 100644 --- a/README.adoc +++ b/README.adoc @@ -652,6 +652,34 @@ the `PortName` key. If you want to specify in which Kubernetes namespace the tar the `KubernetesNamespace` key, remembering in both instances to prefix these keys with your service name and `ribbon` prefix, as specified earlier. + +.Spring Cloud Kubernetes Ribbon Configuration +|=== +|Property Key |Type |Default Value + +|spring.cloud.kubernetes.ribbon.enabled +|boolean +|true + +|spring.cloud.kubernetes.ribbon.mode +|`KubernetesRibbonMode` +|POD + +|spring.cloud.kubernetes.ribbon.cluster-domain +|string +|cluster.local +|=== + +* `spring.cloud.kubernetes.ribbon.mode` supports `POD` and `SERVICE` modes. +** The POD mode is to achieve load balancing by obtaining the Pod IP address of Kubernetes and using Ribbon. + POD mode uses the load balancing of the Ribbon Does not support Kubernetes load balancing, The traffic policy of `Istio` is not supported. +** the `SERVICE` mode is directly based on the `service name` of the Ribbon. Get +The Kubernetes service is concatenated into `service-name.{namespace}.svc.{cluster.domain}:{port}` such as: `demo1.default.svc.cluster.local:8080`. +the `SERVICE` mode uses load balancing of the Kubernetes service to support Istio's traffic policy. + +* `spring.cloud.kubernetes.ribbon.cluster-domain` Set the custom Kubernetes cluster domain suffix. The default value is: 'cluster.local' + + The following examples use this module for ribbon discovery: * link:./spring-cloud-kubernetes-examples/kubernetes-circuitbreaker-ribbon-example[Spring Cloud Circuitbreaker and Ribbon] diff --git a/docs/src/main/asciidoc/ribbon-integration.adoc b/docs/src/main/asciidoc/ribbon-integration.adoc index a9ee311e..967ef343 100644 --- a/docs/src/main/asciidoc/ribbon-integration.adoc +++ b/docs/src/main/asciidoc/ribbon-integration.adoc @@ -45,6 +45,32 @@ the `PortName` key. If you want to specify in which Kubernetes namespace the tar the `KubernetesNamespace` key, remembering in both instances to prefix these keys with your service name and `ribbon` prefix, as specified earlier. +.Spring Cloud Kubernetes Ribbon Configuration +|=== +|Property Key |Type |Default Value + +|spring.cloud.kubernetes.ribbon.enabled +|boolean +|true + +|spring.cloud.kubernetes.ribbon.mode +|`KubernetesRibbonMode` +|POD + +|spring.cloud.kubernetes.ribbon.cluster-domain +|string +|cluster.local +|=== + +* `spring.cloud.kubernetes.ribbon.mode` supports `POD` and `SERVICE` modes. +** The POD mode is to achieve load balancing by obtaining the Pod IP address of Kubernetes and using Ribbon. + POD mode uses the load balancing of the Ribbon Does not support Kubernetes load balancing, The traffic policy of `Istio` is not supported. +** the `SERVICE` mode is directly based on the `service name` of the Ribbon. Get +The Kubernetes service is concatenated into `service-name.{namespace}.svc.{cluster.domain}:{port}` such as: `demo1.default.svc.cluster.local:8080`. +the `SERVICE` mode uses load balancing of the Kubernetes service to support Istio's traffic policy. + +* `spring.cloud.kubernetes.ribbon.cluster-domain` Set the custom Kubernetes cluster domain suffix. The default value is: 'cluster.local' + The following examples use this module for ribbon discovery: * link:./spring-cloud-kubernetes-examples/kubernetes-circuitbreaker-ribbon-example[Spring Cloud Circuitbreaker and Ribbon] diff --git a/spring-cloud-kubernetes-ribbon/pom.xml b/spring-cloud-kubernetes-ribbon/pom.xml index 582f0081..06275909 100644 --- a/spring-cloud-kubernetes-ribbon/pom.xml +++ b/spring-cloud-kubernetes-ribbon/pom.xml @@ -99,6 +99,11 @@ ${groovy.version} test + + org.springframework.boot + spring-boot-configuration-processor + true + diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesEndpointsServerList.java b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesEndpointsServerList.java new file mode 100644 index 00000000..7166564c --- /dev/null +++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesEndpointsServerList.java @@ -0,0 +1,95 @@ +/* + * Copyright 2013-2019 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.ribbon; + +import java.util.ArrayList; +import java.util.List; + +import com.netflix.loadbalancer.Server; +import io.fabric8.kubernetes.api.model.EndpointAddress; +import io.fabric8.kubernetes.api.model.EndpointPort; +import io.fabric8.kubernetes.api.model.EndpointSubset; +import io.fabric8.kubernetes.api.model.Endpoints; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.utils.Utils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * the KubernetesEndpointsServerList description. + * @author wuzishu + */ +public class KubernetesEndpointsServerList extends KubernetesServerList { + + private static final Log LOG = LogFactory.getLog(KubernetesEndpointsServerList.class); + + /** + * Instantiates a new Kubernetes endpoints server list. + * + * @param client the client + * @param properties the properties + */ + KubernetesEndpointsServerList(KubernetesClient client, KubernetesRibbonProperties properties) { + super(client, properties); + } + + @Override + public List getUpdatedListOfServers() { + List result = new ArrayList<>(); + Endpoints endpoints = StringUtils.isNotBlank(this.getNamespace()) + ? this.getClient().endpoints().inNamespace(this.getNamespace()) + .withName(this.getServiceId()).get() + : this.getClient().endpoints().withName(this.getServiceId()).get(); + if (endpoints != null) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format( + "Found [%d] endpoints in l [%s] for name [%s] and portName [%s]", + endpoints.getSubsets().size(), + endpoints.getMetadata().getNamespace(), this.getServiceId(), + this.getPortName())); + } + for (EndpointSubset subset : endpoints.getSubsets()) { + + if (subset.getPorts().size() == 1) { + EndpointPort port = subset.getPorts().get(getFIRST()); + for (EndpointAddress address : subset.getAddresses()) { + result.add(new Server(address.getIp(), port.getPort())); + } + } + else { + for (EndpointPort port : subset.getPorts()) { + if (Utils.isNullOrEmpty(this.getPortName()) + || this.getPortName().endsWith(port.getName())) { + for (EndpointAddress address : subset.getAddresses()) { + result.add( + new Server(address.getIp(), port.getPort())); + } + } + } + } + } + } + if (result.isEmpty()) { + LOG.warn(String.format( + "Did not find any endpoints in ribbon in namespace [%s] for name [%s] and portName [%s]", + this.getNamespace(), this.getServiceId(), this.getPortName())); + } + + return result; + } +} diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonClientConfiguration.java b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonClientConfiguration.java index 7bb5fc5e..aa1018ad 100644 --- a/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonClientConfiguration.java +++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonClientConfiguration.java @@ -21,6 +21,7 @@ import com.netflix.loadbalancer.ServerList; import io.fabric8.kubernetes.client.KubernetesClient; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -30,6 +31,7 @@ import org.springframework.context.annotation.Configuration; * @author Ioannis Canellos */ @Configuration +@EnableConfigurationProperties(KubernetesRibbonProperties.class) public class KubernetesRibbonClientConfiguration { public KubernetesRibbonClientConfiguration() { @@ -37,8 +39,15 @@ public class KubernetesRibbonClientConfiguration { @Bean @ConditionalOnMissingBean - public ServerList ribbonServerList(KubernetesClient client, IClientConfig config) { - KubernetesServerList serverList = new KubernetesServerList(client); + public ServerList ribbonServerList(KubernetesClient client, IClientConfig config, + KubernetesRibbonProperties properties) { + KubernetesServerList serverList; + if (properties.getMode() == KubernetesRibbonMode.SERVICE) { + serverList = new KubernetesServicesServerList(client, properties); + } + else { + serverList = new KubernetesEndpointsServerList(client, properties); + } serverList.initWithNiwsConfig(config); return serverList; } diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonMode.java b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonMode.java new file mode 100644 index 00000000..f3a07e3e --- /dev/null +++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonMode.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2019 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.ribbon; + +/** + * the KubernetesRibbonMode description. + * + * @author wuzishu + */ +public enum KubernetesRibbonMode { + + /** + * using pod ip and port. + */ + POD, + /** + * using kubernetes service name and port. + */ + SERVICE + +} diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonProperties.java b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonProperties.java new file mode 100644 index 00000000..3b0f694f --- /dev/null +++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonProperties.java @@ -0,0 +1,91 @@ +/* + * Copyright 2013-2019 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.ribbon; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * The type Kubernetes ribbon properties. + */ +@ConfigurationProperties(prefix = "spring.cloud.kubernetes.ribbon") +public class KubernetesRibbonProperties { + + /** + * Ribbon enabled,default true. + */ + private Boolean enabled = true; + + /** + * {@link KubernetesRibbonMode} setting ribbon server list with ip of pod or service + * name. default value is POD. + */ + private KubernetesRibbonMode mode = KubernetesRibbonMode.POD; + + /** + * cluster domain. + */ + private String clusterDomain = "cluster.local"; + + /** + * Get cluster domain. + * @return the cluster domain + */ + public String getClusterDomain() { + return clusterDomain; + } + + /** + * Sets cluster domain. + * @param clusterDomain the cluster domain + */ + public void setClusterDomain(String clusterDomain) { + this.clusterDomain = clusterDomain; + } + + /** + * Gets mode. + * @return the mode + */ + public KubernetesRibbonMode getMode() { + return mode; + } + + /** + * Sets mode. + * @param mode the mode + */ + public void setMode(KubernetesRibbonMode mode) { + this.mode = mode; + } + + /** + * Gets enabled. + * @return the enabled + */ + public Boolean getEnabled() { + return enabled; + } + + /** + * Sets enabled. + * @param enabled the enabled + */ + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + +} diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesServerList.java b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesServerList.java index 44d413bf..7c6abc2a 100644 --- a/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesServerList.java +++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesServerList.java @@ -16,7 +16,6 @@ package org.springframework.cloud.kubernetes.ribbon; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -24,27 +23,19 @@ import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractServerList; import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.ServerList; -import io.fabric8.kubernetes.api.model.EndpointAddress; -import io.fabric8.kubernetes.api.model.EndpointPort; -import io.fabric8.kubernetes.api.model.EndpointSubset; -import io.fabric8.kubernetes.api.model.Endpoints; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.utils.Utils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; /** * Kubernetes {@link ServerList}. * * @author Ioannis Canellos + * @author wuzishu */ -public class KubernetesServerList extends AbstractServerList - implements ServerList { +public abstract class KubernetesServerList extends AbstractServerList + implements ServerList { private static final int FIRST = 0; - private static final Log LOG = LogFactory.getLog(KubernetesServerList.class); - private final KubernetesClient client; private String serviceId; @@ -53,62 +44,84 @@ public class KubernetesServerList extends AbstractServerList private String portName; - public KubernetesServerList(KubernetesClient client) { + private KubernetesRibbonProperties properties; + + /** + * Instantiates a new Kubernetes server list. + * + * @param client the client + * @param properties the properties + */ + public KubernetesServerList(KubernetesClient client, + KubernetesRibbonProperties properties) { this.client = client; + this.properties = properties; } public void initWithNiwsConfig(IClientConfig clientConfig) { this.serviceId = clientConfig.getClientName(); this.namespace = clientConfig.getPropertyAsString(KubernetesConfigKey.Namespace, - this.client.getNamespace()); + this.client.getNamespace()); this.portName = clientConfig.getPropertyAsString(KubernetesConfigKey.PortName, - null); + null); } public List getInitialListOfServers() { return Collections.emptyList(); } - public List getUpdatedListOfServers() { - Endpoints endpoints = this.namespace != null - ? this.client.endpoints().inNamespace(this.namespace) - .withName(this.serviceId).get() - : this.client.endpoints().withName(this.serviceId).get(); - List result = new ArrayList(); - if (endpoints != null) { - - if (LOG.isDebugEnabled()) { - LOG.debug("Found [" + endpoints.getSubsets().size() - + "] endpoints in namespace [" + this.namespace + "] for name [" - + this.serviceId + "] and portName [" + this.portName + "]"); - } - for (EndpointSubset subset : endpoints.getSubsets()) { - - if (subset.getPorts().size() == 1) { - EndpointPort port = subset.getPorts().get(FIRST); - for (EndpointAddress address : subset.getAddresses()) { - result.add(new Server(address.getIp(), port.getPort())); - } - } - else { - for (EndpointPort port : subset.getPorts()) { - if (Utils.isNullOrEmpty(this.portName) - || this.portName.endsWith(port.getName())) { - for (EndpointAddress address : subset.getAddresses()) { - result.add(new Server(address.getIp(), port.getPort())); - } - } - } - } - } - } - else { - LOG.warn("Did not find any endpoints in ribbon in namespace [" - + this.namespace + "] for name [" + this.serviceId - + "] and portName [" + this.portName + "]"); - } - return result; + /** + * Gets first. + * + * @return the first + */ + static int getFIRST() { + return FIRST; } + /** + * Gets client. + * + * @return the client + */ + KubernetesClient getClient() { + return client; + } + + /** + * Gets service id. + * + * @return the service id + */ + String getServiceId() { + return serviceId; + } + + /** + * Gets namespace. + * + * @return the namespace + */ + String getNamespace() { + return namespace; + } + + /** + * Gets port name. + * + * @return the port name + */ + String getPortName() { + return portName; + } + + /** + * Gets properties. + * + * @return the properties + */ + KubernetesRibbonProperties getProperties() { + return properties; + } } diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesServicesServerList.java b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesServicesServerList.java new file mode 100644 index 00000000..2c445126 --- /dev/null +++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesServicesServerList.java @@ -0,0 +1,92 @@ +/* + * Copyright 2013-2019 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.ribbon; + +import java.util.ArrayList; +import java.util.List; + +import com.netflix.loadbalancer.Server; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.ServicePort; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.utils.Utils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * the KubernetesServicesServerList description. + * @author wuzishu + */ +public class KubernetesServicesServerList extends KubernetesServerList { + private static final Log LOG = LogFactory.getLog(KubernetesServicesServerList.class); + + /** + * Instantiates a new Kubernetes services server list. + * + * @param client the client + * @param properties the properties + */ + KubernetesServicesServerList(KubernetesClient client, KubernetesRibbonProperties properties) { + super(client, properties); + } + + /** + * Concat service fully qualified domain name. + * @param service Service model + * @return service FQDN + */ + private String concatServiceFQDN(Service service) { + return String.format("%s.%s.svc.%s", service.getMetadata().getName(), + StringUtils.isNotBlank(service.getMetadata().getNamespace()) ? service.getMetadata() + .getNamespace() : "default", this.getProperties().getClusterDomain()); + } + + @Override + public List getUpdatedListOfServers() { + List result = new ArrayList<>(); + Service service = StringUtils.isNotBlank(this.getNamespace()) + ? this.getClient().services().inNamespace(this.getNamespace()) + .withName(this.getServiceId()).get() + : this.getClient().services().withName(this.getServiceId()).get(); + if (service != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Found Service[" + service.getMetadata().getName() + "]"); + } + if (service.getSpec().getPorts().size() == 1) { + result.add(new Server(this.concatServiceFQDN(service), + service.getSpec().getPorts().get(0).getPort())); + } + else { + for (ServicePort servicePort : service.getSpec().getPorts()) { + if (Utils.isNotNullOrEmpty(this.getPortName()) + || this.getPortName().endsWith(servicePort.getName())) { + result.add(new Server(concatServiceFQDN(service), + servicePort.getPort())); + } + } + + } + } + if (result.isEmpty()) { + LOG.warn(String.format( + "Did not find any service in ribbon in namespace [%s] for name [%s] and portName [%s]", + this.getNamespace(), this.getServiceId(), this.getPortName())); + } + return result; + } +} diff --git a/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonWithServiceModeTest.java b/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonWithServiceModeTest.java new file mode 100644 index 00000000..8d75bcc2 --- /dev/null +++ b/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonWithServiceModeTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2013-2019 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.ribbon; + +import java.util.List; + +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; +import io.fabric8.kubernetes.api.model.IntOrString; +import io.fabric8.kubernetes.api.model.ServiceBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesServer; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * the RibbonWithServiceModeTest description. + * @author wuzishu + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestApplication.class, properties = { + "spring.application.name=testapp", + "spring.cloud.kubernetes.client.namespace=testns", + "spring.cloud.kubernetes.client.trustCerts=true", + "spring.cloud.kubernetes.config.namespace=testns", + "spring.cloud.kubernetes.enabled=true", + "spring.cloud.kubernetes.discovery.enabled=true", + "spring.cloud.kubernetes.ribbon.enabled=true", + "spring.cloud.kubernetes.ribbon.mode=SERVICE", + "spring.cloud.kubernetes.ribbon.clusterDomain=test.com"}) +@EnableAutoConfiguration +@EnableDiscoveryClient +public class RibbonWithServiceModeTest { + @ClassRule + public static KubernetesServer server = new KubernetesServer(); + + @ClassRule + public static KubernetesServer mockEndpointA = new KubernetesServer(false); + + private static KubernetesClient mockClient; + + @Autowired + private RestTemplate restTemplate; + + @BeforeClass + public static void setUpBefore() { + mockClient = server.getClient(); + + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, + mockClient.getConfiguration().getMasterUrl()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, + "false"); + + // Configured + server.expect().get().withPath("/api/v1/namespaces/testns/services/testapp").andReturn(200, + new ServiceBuilder().withNewMetadata().withName("testapp").withNamespace("testns") + .endMetadata().withNewSpec().addToSelector("app", "testapp-a") + .addNewPort().withName("http") + .withPort(mockEndpointA.getMockServer().getPort()) + .withTargetPort(new IntOrString(mockEndpointA.getMockServer().getPort())) + .withProtocol("TCP").endPort().endSpec().build()) + .always(); + + } + + @Autowired + private ApplicationContext context; + + @Test + public void testGreetingWithServiceMode() { + SpringClientFactory springClientFactory = context.getBean(SpringClientFactory.class); + ILoadBalancer testapp = springClientFactory.getLoadBalancer("testapp"); + List allServers = testapp.getAllServers(); + assertThat(allServers.stream().map(c -> String.format("%s:%s", c.getHost(), c.getPort()))) + .containsOnly("testapp.testns.svc.test.com:" + mockEndpointA.getMockServer().getPort()); + } +}