First draft of ribbon support.

This commit is contained in:
Ioannis Canellos
2016-03-30 15:31:10 +03:00
parent 6777e43b69
commit 149541e45f
9 changed files with 462 additions and 0 deletions

18
pom.xml
View File

@@ -93,6 +93,7 @@
<module>spring-cloud-kubernetes-discovery</module>
<module>spring-cloud-kubernetes-core</module>
<module>spring-cloud-starter-kubernetes</module>
<module>spring-cloud-kubernetes-ribbon</module>
</modules>
<dependencyManagement>
@@ -103,22 +104,39 @@
<artifactId>spring-cloud-kubernetes-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>spring-cloud-kubernetes-ribbon</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>${kubernetes-client.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-dependencies</artifactId>
<version>${spring-cloud-netflix.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2016 to the original 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
http://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="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-kubernetes-project</artifactId>
<groupId>io.fabric8</groupId>
<version>0.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>io.fabric8</groupId>
<artifactId>spring-cloud-kubernetes-ribbon</artifactId>
<name>Fabric8 :: Spring Cloud Kubernetes :: Ribbon</name>
<dependencies>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.netflix.archaius</groupId>
<artifactId>archaius-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-httpclient</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-loadbalancer</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,116 @@
/*
* Copyright (C) 2016 to the original 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
*
* http://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 io.fabric8.spring.cloud.kubernetes.ribbon;
import com.google.common.reflect.TypeToken;
import com.netflix.client.config.IClientConfigKey;
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 static com.google.common.base.Preconditions.checkArgument;
public abstract class KubernetesConfigKey<T> implements IClientConfigKey<T> {
public static final IClientConfigKey<String> Namespace = new KubernetesConfigKey<String>("KubernetesNamespace"){};
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);
}
}
}
}
/**
* @deprecated see {@link #keys()}
*/
@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;
}
};
}
private final String configKey;
private final Class<T> type;
@SuppressWarnings("unchecked")
protected KubernetesConfigKey(String configKey) {
this.configKey = configKey;
Type superclass = getClass().getGenericSuperclass();
checkArgument(superclass instanceof ParameterizedType,
"%s isn't parameterized", superclass);
Type runtimeType = ((ParameterizedType) superclass).getActualTypeArguments()[0];
type = (Class<T>) TypeToken.of(runtimeType).getRawType();
}
@Override
public Class<T> type() {
return type;
}
/* (non-Javadoc)
* @see com.netflix.niws.client.ClientConfig#key()
*/
@Override
public String key() {
return configKey;
}
@Override
public String toString() {
return configKey;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2016 to the original 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
*
* http://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 io.fabric8.spring.cloud.kubernetes.ribbon;
import com.netflix.client.config.IClientConfig;
import com.netflix.config.ConfigurationManager;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.netflix.loadbalancer.ServerListFilter;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.spring.cloud.discovery.KubernetesDiscoveryProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.config.DynamicStringProperty;
import static com.netflix.client.config.CommonClientConfigKey.DeploymentContextBasedVipAddresses;
import static com.netflix.client.config.CommonClientConfigKey.EnableZoneAffinity;
@Configuration
public class KubernetesRibbonClientConfiguration {
public KubernetesRibbonClientConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(KubernetesClient client, IClientConfig config) {
KubernetesServerList serverList = new KubernetesServerList(client);
serverList.initWithNiwsConfig(config);
return serverList;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2016 to the original 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
*
* http://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 io.fabric8.spring.cloud.kubernetes.ribbon;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractServerList;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import io.fabric8.kubernetes.api.model.EndpointAddress;
import io.fabric8.kubernetes.api.model.EndpointPort;
import io.fabric8.kubernetes.api.model.EndpointSubset;
import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class KubernetesServerList extends AbstractServerList<Server> implements ServerList<Server> {
private static final int FIRST = 0;
private static final Logger LOG = LoggerFactory.getLogger(KubernetesServerList.class);
private final KubernetesClient client;
private String serviceId;
private String namespace;
private String portName;
public KubernetesServerList(KubernetesClient client) {
this.client = client;
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
this.serviceId = clientConfig.getClientName();
this.namespace = clientConfig.getPropertyAsString(KubernetesConfigKey.Namespace, client.getNamespace());
this.portName = clientConfig.getPropertyAsString(KubernetesConfigKey.PortName, null);
}
public List<Server> getInitialListOfServers() {
return Collections.emptyList();
}
public List<Server> getUpdatedListOfServers() {
Endpoints endpoints = namespace != null
? client.endpoints().inNamespace(namespace).withName(serviceId).get()
: client.endpoints().withName(serviceId).get();
List<Server> result = new ArrayList<Server>();
if (endpoints != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Found [" + endpoints.getSubsets().size() + "] endpoints in namespace [" +
namespace + "] for name [" + serviceId + "] and portName [" + portName + "]");
}
for (EndpointSubset subset : endpoints.getSubsets()) {
if (subset.getPorts().size() == 1) {
EndpointPort port = subset.getPorts().get(FIRST);
for (EndpointAddress address : subset.getAddresses()) {
result.add(new Server(address.getIp(), port.getPort()));
}
} else {
for (EndpointPort port : subset.getPorts()) {
if (Utils.isNullOrEmpty(portName) || portName.endsWith(port.getName())) {
for (EndpointAddress address : subset.getAddresses()) {
result.add(new Server(address.getIp(), port.getPort()));
}
}
}
}
}
} else {
LOG.warn("Did not find any endpoints in ribbon in namespace [" + namespace + "] for name [" +
serviceId + "] and portName [" + portName + "]");
}
return result;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2016 to the original 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
*
* http://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 io.fabric8.spring.cloud.kubernetes.ribbon;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Map;
@ConfigurationProperties("ribbon")
public class RibbonConfigurationProperties {
private Map<String, Override> overrides;
static class Override {
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2016 to the original 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
*
* http://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 io.fabric8.spring.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;
@Configuration
@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,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.fabric8.spring.cloud.kubernetes.ribbon.RibbonKubernetesAutoConfiguration

View File

@@ -39,6 +39,11 @@
<groupId>io.fabric8</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>spring-cloud-kubernetes-ribbon</artifactId>
</dependency>
</dependencies>
</project>