Revert "removes ribbon support"

This reverts commit 78a306c083.
This commit is contained in:
Ryan Baxter
2020-06-23 14:31:35 -04:00
parent 79ced288de
commit 9267796e4b
19 changed files with 1423 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2018 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.
~
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-kubernetes</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-ribbon</artifactId>
<name>Spring Cloud Kubernetes :: Ribbon</name>
<properties>
<service.occurrence>1</service.occurrence>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-server-mock</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-Dservice</argLine>
<systemPropertyVariables>
<service.occurrence>${service.occurrence}</service.occurrence>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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 <T> type of key
* @author Ioannis Canellos
*/
public abstract class KubernetesConfigKey<T> implements IClientConfigKey<T> {
/**
* Namespace configuration key.
*/
public static final IClientConfigKey<String> Namespace = new KubernetesConfigKey<String>(
"KubernetesNamespace") {
};
/**
* Port name configuration key.
*/
public static final IClientConfigKey<String> PortName = new KubernetesConfigKey<String>(
"PortName") {
};
private static final Set<IClientConfigKey> keys = new HashSet<IClientConfigKey>();
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<T> 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<T>) 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<IClientConfigKey> 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<T> 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;
}
}

View File

@@ -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<Server> getUpdatedListOfServers() {
List<Server> 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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
}

View File

@@ -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;
}
}

View File

@@ -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<Server>
implements ServerList<Server> {
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<Server> 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;
}
}

View File

@@ -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<Server> getUpdatedListOfServers() {
List<Server> 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;
}
}

View File

@@ -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 {
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.kubernetes.ribbon.RibbonKubernetesAutoConfiguration

View File

@@ -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 <T extends TypeOne, I> void testTypes() {
// with class
KubernetesConfigKey<String> key1 = new KubernetesConfigKey<String>("key1") {
};
// with type variable
KubernetesConfigKey<T> key2 = new KubernetesConfigKey<T>("key2") {
};
// with type variable with no bounds
KubernetesConfigKey<I> key3 = new KubernetesConfigKey<I>("key3") {
};
assertThat(key1.type()).isEqualTo(String.class);
assertThat(key2.type()).isEqualTo(TypeOne.class);
assertThat(key3.type()).isEqualTo(Object.class);
}
private class TypeOne<T> {
}
}

View File

@@ -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);
}
}

View File

@@ -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<String> 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");
}
}

View File

@@ -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<Server> 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());
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,5 @@
testapp:
ribbon:
eureka:
enabled: false
ServerListRefreshInterval: 500

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<logger name="org.hibernate.validator"
level="info"/> <!-- Validator prints a lot of debug messages during integration tests -->
<logger name="okhttp3.mockwebserver" level="debug"/>
</configuration>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2018 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.
~
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-kubernetes</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
<name>Spring Cloud Kubernetes :: Starter :: Ribbon</name>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-ribbon</artifactId>
</dependency>
</dependencies>
</project>