From 9267796e4b3e3de6be773da47e447b6b44d9e420 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Tue, 23 Jun 2020 14:31:35 -0400 Subject: [PATCH] Revert "removes ribbon support" This reverts commit 78a306c08362553211faaebfc073e7e0e9549f36. --- spring-cloud-kubernetes-ribbon/pom.xml | 124 ++++++++++++ .../ribbon/KubernetesConfigKey.java | 138 +++++++++++++ .../ribbon/KubernetesEndpointsServerList.java | 96 +++++++++ .../KubernetesRibbonClientConfiguration.java | 55 ++++++ .../ribbon/KubernetesRibbonMode.java | 35 ++++ .../ribbon/KubernetesRibbonProperties.java | 91 +++++++++ .../ribbon/KubernetesServerList.java | 120 ++++++++++++ .../ribbon/KubernetesServicesServerList.java | 96 +++++++++ .../RibbonKubernetesAutoConfiguration.java | 42 ++++ .../cloud/kubernetes/ribbon/Types.java | 55 ++++++ .../main/resources/META-INF/spring.factories | 2 + .../ribbon/KubernetesConfigKeyTest.java | 48 +++++ .../kubernetes/ribbon/RibbonFallbackTest.java | 185 ++++++++++++++++++ .../cloud/kubernetes/ribbon/RibbonTest.java | 114 +++++++++++ .../ribbon/RibbonWithServiceModeTest.java | 116 +++++++++++ .../kubernetes/ribbon/TestApplication.java | 41 ++++ .../src/test/resources/application.yml | 5 + .../src/test/resources/logback-test.xml | 7 + .../pom.xml | 53 +++++ 19 files changed, 1423 insertions(+) create mode 100644 spring-cloud-kubernetes-ribbon/pom.xml create mode 100644 spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/KubernetesConfigKey.java 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/KubernetesRibbonClientConfiguration.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/KubernetesServerList.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/main/java/org/springframework/cloud/kubernetes/ribbon/RibbonKubernetesAutoConfiguration.java create mode 100644 spring-cloud-kubernetes-ribbon/src/main/java/org/springframework/cloud/kubernetes/ribbon/Types.java create mode 100644 spring-cloud-kubernetes-ribbon/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/KubernetesConfigKeyTest.java create mode 100644 spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonFallbackTest.java create mode 100644 spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonTest.java create mode 100644 spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/RibbonWithServiceModeTest.java create mode 100644 spring-cloud-kubernetes-ribbon/src/test/java/org/springframework/cloud/kubernetes/ribbon/TestApplication.java create mode 100644 spring-cloud-kubernetes-ribbon/src/test/resources/application.yml create mode 100644 spring-cloud-kubernetes-ribbon/src/test/resources/logback-test.xml create mode 100644 spring-cloud-starter-kubernetes-ribbon/pom.xml 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 + + + +