diff --git a/spring-cloud-kubernetes-ribbon/pom.xml b/spring-cloud-kubernetes-ribbon/pom.xml
new file mode 100644
index 00000000..b28f816b
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/pom.xml
@@ -0,0 +1,124 @@
+
+
+
+
+
+ spring-cloud-kubernetes
+ org.springframework.cloud
+ 2.0.0.BUILD-SNAPSHOT
+
+ 4.0.0
+
+ org.springframework.cloud
+ spring-cloud-kubernetes-ribbon
+ Spring Cloud Kubernetes :: Ribbon
+
+
+ 1
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-kubernetes-discovery
+ ${project.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ true
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-ribbon
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ test
+
+
+
+ io.fabric8
+ kubernetes-client
+ test-jar
+ test
+
+
+
+ io.fabric8
+ kubernetes-server-mock
+ test
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+
+
+ org.spockframework
+ spock-spring
+ test
+
+
+ org.codehaus.groovy
+ groovy-all
+ ${groovy.version}
+ test
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ -Dservice
+
+ ${service.occurrence}
+
+
+
+
+
+
+
diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesConfigKey.java b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesConfigKey.java
new file mode 100644
index 00000000..ec706bb2
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesConfigKey.java
@@ -0,0 +1,138 @@
+/*
+ * 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.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.netflix.client.config.IClientConfigKey;
+
+import org.springframework.util.Assert;
+
+/**
+ * Kubernetes implementation of a Ribbon {@link IClientConfigKey}.
+ *
+ * @param type of key
+ * @author Ioannis Canellos
+ */
+public abstract class KubernetesConfigKey implements IClientConfigKey {
+
+ /**
+ * Namespace configuration key.
+ */
+ public static final IClientConfigKey Namespace = new KubernetesConfigKey(
+ "KubernetesNamespace") {
+ };
+
+ /**
+ * Port name configuration key.
+ */
+ public static final IClientConfigKey PortName = new KubernetesConfigKey(
+ "PortName") {
+ };
+
+ private static final Set keys = new HashSet();
+
+ static {
+ for (Field f : KubernetesConfigKey.class.getDeclaredFields()) {
+ if (Modifier.isStatic(f.getModifiers()) // &&
+ // Modifier.isPublic(f.getModifiers())
+ && IClientConfigKey.class.isAssignableFrom(f.getType())) {
+ try {
+ keys.add((IClientConfigKey) f.get(null));
+ }
+ catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ private final String configKey;
+
+ private final Class type;
+
+ @SuppressWarnings("unchecked")
+ protected KubernetesConfigKey(String configKey) {
+ this.configKey = configKey;
+ Type superclass = getClass().getGenericSuperclass();
+ Assert.isTrue(superclass instanceof ParameterizedType,
+ superclass + " isn't parameterized");
+ Type runtimeType = ((ParameterizedType) superclass).getActualTypeArguments()[0];
+ this.type = (Class) Types.rawType(runtimeType);
+ }
+
+ /**
+ * @deprecated see {@link #keys()}
+ * @return array of {@link IClientConfigKey}
+ */
+ @Deprecated
+ public static IClientConfigKey[] values() {
+ return keys().toArray(new IClientConfigKey[0]);
+ }
+
+ /**
+ * @return all the public static keys defined in this class
+ */
+ public static Set keys() {
+ return keys;
+ }
+
+ public static IClientConfigKey valueOf(final String name) {
+ for (IClientConfigKey key : keys()) {
+ if (key.key().equals(name)) {
+ return key;
+ }
+ }
+ return new IClientConfigKey() {
+ @Override
+ public String key() {
+ return name;
+ }
+
+ @Override
+ public Class type() {
+ return String.class;
+ }
+ };
+ }
+
+ @Override
+ public Class type() {
+ return this.type;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.netflix.niws.client.ClientConfig#key()
+ */
+ @Override
+ public String key() {
+ return this.configKey;
+ }
+
+ @Override
+ public String toString() {
+ return this.configKey;
+ }
+
+}
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..59257e45
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesEndpointsServerList.java
@@ -0,0 +1,96 @@
+/*
+ * 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
new file mode 100644
index 00000000..dfaefb43
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesRibbonClientConfiguration.java
@@ -0,0 +1,55 @@
+/*
+ * 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 com.netflix.client.config.IClientConfig;
+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;
+
+/**
+ * Kubernetes version of a Ribbon client configuration.
+ *
+ * @author Ioannis Canellos
+ */
+@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties(KubernetesRibbonProperties.class)
+public class KubernetesRibbonClientConfiguration {
+
+ public KubernetesRibbonClientConfiguration() {
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ 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
new file mode 100644
index 00000000..7c3232f0
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesServerList.java
@@ -0,0 +1,120 @@
+/*
+ * 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.Collections;
+import java.util.List;
+
+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.client.KubernetesClient;
+
+/**
+ * Kubernetes {@link ServerList}.
+ *
+ * @author Ioannis Canellos
+ * @author wuzishu
+ */
+public abstract class KubernetesServerList extends AbstractServerList
+ implements ServerList {
+
+ private static final int FIRST = 0;
+
+ private final KubernetesClient client;
+
+ private String serviceId;
+
+ private String namespace;
+
+ private String portName;
+
+ 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.portName = clientConfig.getPropertyAsString(KubernetesConfigKey.PortName,
+ null);
+ }
+
+ public List getInitialListOfServers() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * 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..5d0d6a69
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesServicesServerList.java
@@ -0,0 +1,96 @@
+/*
+ * 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/main/java/org/springframework/cloud/kubernetes/ribbon/RibbonKubernetesAutoConfiguration.java b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/RibbonKubernetesAutoConfiguration.java
new file mode 100644
index 00000000..d10c1e9d
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/RibbonKubernetesAutoConfiguration.java
@@ -0,0 +1,42 @@
+/*
+ * 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.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
+import org.springframework.cloud.netflix.ribbon.RibbonClients;
+import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Auto configuration for Ribbon.
+ *
+ * @author Ioannis Canellos
+ */
+@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties
+@ConditionalOnBean(SpringClientFactory.class)
+@ConditionalOnProperty(value = "spring.cloud.kubernetes.ribbon.enabled",
+ matchIfMissing = true)
+@AutoConfigureAfter(RibbonAutoConfiguration.class)
+@RibbonClients(defaultConfiguration = KubernetesRibbonClientConfiguration.class)
+public class RibbonKubernetesAutoConfiguration {
+
+}
diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/Types.java b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/Types.java
new file mode 100644
index 00000000..b92faa47
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/Types.java
@@ -0,0 +1,55 @@
+/*
+ * 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.lang.reflect.GenericArrayType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+
+final class Types {
+
+ private Types() {
+ // Utlity
+ }
+
+ static Class rawType(Type type) {
+ if (type instanceof Class) {
+ return (Class) type;
+ }
+ else if (type instanceof TypeVariable) {
+ return rawType(firstOrObject(((TypeVariable) type).getBounds()));
+ }
+ else if (type instanceof WildcardType) {
+ return rawType(firstOrObject(((WildcardType) type).getUpperBounds()));
+ }
+ else if (type instanceof GenericArrayType) {
+ return rawType(((GenericArrayType) type).getGenericComponentType());
+ }
+ return Object.class;
+ }
+
+ private static Type firstOrObject(Type[] types) {
+ if (types.length > 0) {
+ return rawType(types[0]);
+ }
+ else {
+ return Void.class;
+ }
+ }
+
+}
diff --git a/spring-cloud-kubernetes-ribbon/src/main/resources/META-INF/spring.factories b/spring-cloud-kubernetes-ribbon/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000..e118428d
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.springframework.cloud.kubernetes.ribbon.RibbonKubernetesAutoConfiguration
diff --git a/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/KubernetesConfigKeyTest.java b/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/KubernetesConfigKeyTest.java
new file mode 100644
index 00000000..ca20007c
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/KubernetesConfigKeyTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class KubernetesConfigKeyTest {
+
+ @Test
+ public void testTypes() {
+ // with class
+ KubernetesConfigKey key1 = new KubernetesConfigKey("key1") {
+ };
+
+ // with type variable
+ KubernetesConfigKey key2 = new KubernetesConfigKey("key2") {
+ };
+
+ // with type variable with no bounds
+ KubernetesConfigKey key3 = new KubernetesConfigKey("key3") {
+ };
+
+ assertThat(key1.type()).isEqualTo(String.class);
+ assertThat(key2.type()).isEqualTo(TypeOne.class);
+ assertThat(key3.type()).isEqualTo(Object.class);
+ }
+
+ private class TypeOne {
+
+ }
+
+}
diff --git a/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonFallbackTest.java b/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonFallbackTest.java
new file mode 100644
index 00000000..6422d641
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonFallbackTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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 io.fabric8.kubernetes.api.model.Endpoints;
+import io.fabric8.kubernetes.api.model.EndpointsBuilder;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.server.mock.KubernetesServer;
+import io.fabric8.mockwebserver.DefaultMockServer;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+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.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.web.client.RestTemplate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+/**
+ * @author Charles Moulliard
+ */
+@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" })
+@EnableAutoConfiguration
+@EnableDiscoveryClient
+public class RibbonFallbackTest {
+
+ private static final Log LOG = LogFactory.getLog(RibbonFallbackTest.class);
+
+ @ClassRule
+ public static KubernetesServer mockServer = new KubernetesServer(false);
+
+ public static DefaultMockServer mockEndpoint;
+
+ public static KubernetesClient mockClient;
+
+ @Autowired
+ RestTemplate restTemplate;
+
+ @Value("${service.occurrence}")
+ private int serviceOccurrence;
+
+ @Value("${testapp.ribbon.ServerListRefreshInterval}")
+ private int serverListRefreshInterval;
+
+ @BeforeClass
+ public static void setUpBefore() throws Exception {
+ mockClient = mockServer.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");
+ System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
+
+ mockEndpoint = new DefaultMockServer(false);
+ mockEndpoint.start();
+ }
+
+ public static Endpoints newEndpoint(String name, String namespace,
+ DefaultMockServer mockServer) {
+ // @formatter:off
+ return new EndpointsBuilder()
+ .withNewMetadata()
+ .withName(name)
+ .withNamespace(namespace)
+ .endMetadata()
+ .addNewSubset()
+ .addNewAddress()
+ .withIp(mockServer.getHostName())
+ .endAddress()
+ .addNewPort("http", mockServer.getPort(), "http")
+ .endSubset()
+ .build();
+ // @formatter:on
+ }
+
+ @Test
+ public void testFallBackGreetingEndpoint() {
+ /**
+ * Scenario tested 1. Register the mock endpoint of the service into
+ * KubeMockServer and call /greeting service 2. Unregister the mock endpoint and
+ * verify that Ribbon doesn't have any instances anymore in its list 3. Re
+ * register the mock endpoint and play step 1)
+ **/
+
+ LOG.info(">>>>>>>>>> BEGIN PART 1 <<<<<<<<<<<<<");
+
+ // As Ribbon refreshes its list every serverListRefreshInterval ms,
+ // we configure the API Server endpoint to reply to exactly serviceOccurrence
+ // attempts
+ // to be sure that Ribbon will get the mockendpoint to access it for the call
+ mockServer.expect().get().withPath("/api/v1/namespaces/testns/endpoints/testapp")
+ .andReturn(200, newEndpoint("testapp-a", "testns", mockEndpoint))
+ .times(this.serviceOccurrence);
+
+ mockEndpoint.expect().get().withPath("/greeting").andReturn(200, "Hello from A")
+ .once();
+
+ String response = this.restTemplate.getForObject("http://testapp/greeting",
+ String.class);
+ assertThat(response).isEqualTo("Hello from A");
+ LOG.info(">>>>>>>>>> END PART 1 <<<<<<<<<<<<<");
+
+ LOG.info(">>>>>>>>>> BEGIN PART 2 <<<<<<<<<<<<<");
+ try {
+ ensureEndpointsNoLongerReturnedByAPIServer();
+ this.restTemplate.getForObject("http://testapp/greeting", String.class);
+ fail("Ribbon was supposed to throw an Exception due to not knowing of any endpoints to route the request to");
+ }
+ catch (Exception e) {
+ // No endpoint is available anymore and Ribbon list is empty
+ assertThat(e.getMessage()).isEqualTo("No instances available for testapp");
+ }
+ LOG.info(">>>>>>>>>> END PART 2 <<<<<<<<<<<<<");
+
+ LOG.info(">>>>>>>>>> BEGIN PART 3 <<<<<<<<<<<<<");
+ mockServer.expect().get().withPath("/api/v1/namespaces/testns/endpoints/testapp")
+ .andReturn(200, newEndpoint("testapp-a", "testns", mockEndpoint))
+ .always();
+
+ // the purpose of sleeping here is to make sure that even after some refreshes to
+ // it's list
+ // Ribbon still has endpoints to route to
+ // This is different than the first part of the test because the API server has
+ // now been
+ // configured to always respond with some endpoints as opposed to only a certain
+ // amount of
+ // requests which was the case in part 1
+ try {
+ Thread.sleep(2000);
+ }
+ catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+
+ mockEndpoint.expect().get().withPath("/greeting").andReturn(200, "Hello from A")
+ .once();
+ response = this.restTemplate.getForObject("http://testapp/greeting",
+ String.class);
+ assertThat(response).isEqualTo("Hello from A");
+ LOG.info(">>>>>>>>>> END PART 3 <<<<<<<<<<<<<");
+ }
+
+ // This works because the (mock) API server is configured to return the endpoints
+ // exactly
+ // serviceOccurrence times while Ribbon refreshes it's list every
+ // serverListRefreshInterval milliseconds
+ private void ensureEndpointsNoLongerReturnedByAPIServer()
+ throws InterruptedException {
+ Thread.sleep((this.serviceOccurrence + 1) * this.serverListRefreshInterval);
+ }
+
+}
diff --git a/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonTest.java b/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonTest.java
new file mode 100644
index 00000000..08514363
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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 io.fabric8.kubernetes.api.model.EndpointsBuilder;
+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.test.context.junit4.SpringRunner;
+import org.springframework.web.client.RestTemplate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Charles Moulliard
+ */
+@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" })
+@EnableAutoConfiguration
+@EnableDiscoveryClient
+public class RibbonTest {
+
+ @ClassRule
+ public static KubernetesServer server = new KubernetesServer();
+
+ @ClassRule
+ public static KubernetesServer mockEndpointA = new KubernetesServer(false);
+
+ @ClassRule
+ public static KubernetesServer mockEndpointB = 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");
+ System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
+
+ // Configured
+ server.expect().get().withPath("/api/v1/namespaces/testns/endpoints/testapp")
+ .andReturn(200,
+ new EndpointsBuilder().withNewMetadata().withName("testapp-a")
+ .endMetadata().addNewSubset().addNewAddress()
+ .withIp(mockEndpointA.getMockServer().getHostName())
+ .endAddress()
+ .addNewPort("http",
+ mockEndpointA.getMockServer().getPort(), "http")
+ .endSubset().addNewSubset().addNewAddress()
+ .withIp(mockEndpointB.getMockServer().getHostName())
+ .endAddress()
+ .addNewPort("http",
+ mockEndpointB.getMockServer().getPort(), "http")
+ .endSubset().build())
+ .always();
+
+ mockEndpointA.expect().get().withPath("/greeting").andReturn(200, "Hello from A")
+ .always();
+ mockEndpointB.expect().get().withPath("/greeting").andReturn(200, "Hello from B")
+ .always();
+ }
+
+ @Test
+ public void testGreetingEndpoint() {
+ final List greetings = new ArrayList<>();
+ greetings.add(
+ this.restTemplate.getForObject("http://testapp/greeting", String.class));
+ greetings.add(
+ this.restTemplate.getForObject("http://testapp/greeting", String.class));
+
+ assertThat(greetings).containsOnly("Hello from A", "Hello from B");
+ }
+
+}
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..dcd96216
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonWithServiceModeTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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");
+ System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true");
+
+ // 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());
+ }
+
+}
diff --git a/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/TestApplication.java b/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/TestApplication.java
new file mode 100644
index 00000000..10564b7d
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/TestApplication.java
@@ -0,0 +1,41 @@
+/*
+ * 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.SpringApplication;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * @author Charles Moulliard
+ */
+@SpringBootConfiguration
+public class TestApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(TestApplication.class, args);
+ }
+
+ @LoadBalanced
+ @Bean
+ public RestTemplate restTemplate() {
+ return new RestTemplate();
+ }
+
+}
diff --git a/spring-cloud-kubernetes-ribbon/src/test/resources/application.yml b/spring-cloud-kubernetes-ribbon/src/test/resources/application.yml
new file mode 100644
index 00000000..1e92e1a3
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/test/resources/application.yml
@@ -0,0 +1,5 @@
+testapp:
+ ribbon:
+ eureka:
+ enabled: false
+ ServerListRefreshInterval: 500
diff --git a/spring-cloud-kubernetes-ribbon/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-ribbon/src/test/resources/logback-test.xml
new file mode 100644
index 00000000..5d1ac6d0
--- /dev/null
+++ b/spring-cloud-kubernetes-ribbon/src/test/resources/logback-test.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/spring-cloud-starter-kubernetes-ribbon/pom.xml b/spring-cloud-starter-kubernetes-ribbon/pom.xml
new file mode 100644
index 00000000..9d0d65ab
--- /dev/null
+++ b/spring-cloud-starter-kubernetes-ribbon/pom.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+ spring-cloud-kubernetes
+ org.springframework.cloud
+ 2.0.0.BUILD-SNAPSHOT
+
+ 4.0.0
+
+ org.springframework.cloud
+ spring-cloud-starter-kubernetes-ribbon
+ Spring Cloud Kubernetes :: Starter :: Ribbon
+
+
+
+ org.springframework.cloud
+ spring-cloud-kubernetes-core
+
+
+
+ org.springframework.cloud
+ spring-cloud-kubernetes-discovery
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-ribbon
+
+
+ org.springframework.cloud
+ spring-cloud-kubernetes-ribbon
+
+
+
+