From 149541e45fa95ea79eb529667223f3511f885deb Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Wed, 30 Mar 2016 15:31:10 +0300 Subject: [PATCH] First draft of ribbon support. --- pom.xml | 18 +++ spring-cloud-kubernetes-ribbon/pom.xml | 97 +++++++++++++++ .../ribbon/KubernetesConfigKey.java | 116 ++++++++++++++++++ .../KubernetesRibbonClientConfiguration.java | 56 +++++++++ .../ribbon/KubernetesServerList.java | 100 +++++++++++++++ .../ribbon/RibbonConfigurationProperties.java | 32 +++++ .../RibbonKubernetesAutoConfiguration.java | 36 ++++++ .../main/resources/META-INF/spring.factories | 2 + spring-cloud-starter-kubernetes/pom.xml | 5 + 9 files changed, 462 insertions(+) create mode 100644 spring-cloud-kubernetes-ribbon/pom.xml create mode 100644 spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/KubernetesConfigKey.java create mode 100644 spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/KubernetesRibbonClientConfiguration.java create mode 100644 spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/KubernetesServerList.java create mode 100644 spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/RibbonConfigurationProperties.java create mode 100644 spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/RibbonKubernetesAutoConfiguration.java create mode 100644 spring-cloud-kubernetes-ribbon/src/main/resources/META-INF/spring.factories diff --git a/pom.xml b/pom.xml index 4118d2fc..67074597 100644 --- a/pom.xml +++ b/pom.xml @@ -93,6 +93,7 @@ spring-cloud-kubernetes-discovery spring-cloud-kubernetes-core spring-cloud-starter-kubernetes + spring-cloud-kubernetes-ribbon @@ -103,22 +104,39 @@ spring-cloud-kubernetes-core ${project.version} + io.fabric8 spring-cloud-kubernetes-discovery ${project.version} + + io.fabric8 + spring-cloud-kubernetes-ribbon + ${project.version} + + io.fabric8 kubernetes-client ${kubernetes-client.version} + + + org.springframework.cloud + spring-cloud-netflix-dependencies + ${spring-cloud-netflix.version} + import + pom + + org.springframework.boot spring-boot-actuator ${spring-boot.version} + org.springframework.boot spring-boot-autoconfigure diff --git a/spring-cloud-kubernetes-ribbon/pom.xml b/spring-cloud-kubernetes-ribbon/pom.xml new file mode 100644 index 00000000..563d032c --- /dev/null +++ b/spring-cloud-kubernetes-ribbon/pom.xml @@ -0,0 +1,97 @@ + + + + + + spring-cloud-kubernetes-project + io.fabric8 + 0.0.2-SNAPSHOT + + 4.0.0 + + io.fabric8 + spring-cloud-kubernetes-ribbon + Fabric8 :: Spring Cloud Kubernetes :: Ribbon + + + + + io.fabric8 + spring-cloud-kubernetes-discovery + ${project.version} + + + + org.springframework.boot + spring-boot-autoconfigure + true + + + + org.springframework.cloud + spring-cloud-commons + true + + + + org.springframework.cloud + spring-cloud-context + true + + + + org.springframework.cloud + spring-cloud-netflix-core + true + + + + com.netflix.archaius + archaius-core + true + + + + com.netflix.ribbon + ribbon + true + + + + com.netflix.ribbon + ribbon-core + true + + + + com.netflix.ribbon + ribbon-httpclient + true + + + + com.netflix.ribbon + ribbon-loadbalancer + true + + + + + \ No newline at end of file diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/KubernetesConfigKey.java b/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/KubernetesConfigKey.java new file mode 100644 index 00000000..e2dd5f1a --- /dev/null +++ b/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/KubernetesConfigKey.java @@ -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 implements IClientConfigKey { + + public static final IClientConfigKey Namespace = new KubernetesConfigKey("KubernetesNamespace"){}; + 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); + } + } + } + } + + /** + * @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 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 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) TypeToken.of(runtimeType).getRawType(); + } + + @Override + public Class type() { + return type; + } + + /* (non-Javadoc) + * @see com.netflix.niws.client.ClientConfig#key() + */ + @Override + public String key() { + return configKey; + } + + @Override + public String toString() { + return configKey; + } +} diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/KubernetesRibbonClientConfiguration.java b/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/KubernetesRibbonClientConfiguration.java new file mode 100644 index 00000000..0fe21860 --- /dev/null +++ b/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/KubernetesRibbonClientConfiguration.java @@ -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; + } + +} diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/KubernetesServerList.java b/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/KubernetesServerList.java new file mode 100644 index 00000000..67176972 --- /dev/null +++ b/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/KubernetesServerList.java @@ -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 implements ServerList { + + 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 getInitialListOfServers() { + return Collections.emptyList(); + } + + public List getUpdatedListOfServers() { + Endpoints endpoints = namespace != null + ? client.endpoints().inNamespace(namespace).withName(serviceId).get() + : client.endpoints().withName(serviceId).get(); + + List result = new ArrayList(); + 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; + } +} diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/RibbonConfigurationProperties.java b/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/RibbonConfigurationProperties.java new file mode 100644 index 00000000..b431c06f --- /dev/null +++ b/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/RibbonConfigurationProperties.java @@ -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 overrides; + + static class Override { + + } +} diff --git a/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/RibbonKubernetesAutoConfiguration.java b/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/RibbonKubernetesAutoConfiguration.java new file mode 100644 index 00000000..be2a0038 --- /dev/null +++ b/spring-cloud-kubernetes-ribbon/src/main/java/io/fabric8/spring/cloud/kubernetes/ribbon/RibbonKubernetesAutoConfiguration.java @@ -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 { +} 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..c93cffa8 --- /dev/null +++ b/spring-cloud-kubernetes-ribbon/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +io.fabric8.spring.cloud.kubernetes.ribbon.RibbonKubernetesAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-starter-kubernetes/pom.xml b/spring-cloud-starter-kubernetes/pom.xml index 8c10c9ae..487606bd 100644 --- a/spring-cloud-starter-kubernetes/pom.xml +++ b/spring-cloud-starter-kubernetes/pom.xml @@ -39,6 +39,11 @@ io.fabric8 spring-cloud-kubernetes-discovery + + + io.fabric8 + spring-cloud-kubernetes-ribbon + \ No newline at end of file