From 5ab50de47ff13cc613ac7ac818a0843d66bdb5c1 Mon Sep 17 00:00:00 2001 From: qiyi Date: Sat, 3 Mar 2018 17:43:44 +0800 Subject: [PATCH] Change the version to 2.0.0.BUILD-SNAPSHOT to support Spring Boot 2.0.0.RELEASE and Spring Cloud 2.0.0.x, spring-cloud-incubator/spring-cloud-etcd#46 1. Change the version to 2.0.0.BUILD-SNAPSHOT 2. Merge the #43 from @venilnoronha for the EtcdLifecycle can't work now 3. Upgrade the etcd version to v3.3.1 for ci --- README.adoc | 2 +- docs/pom.xml | 4 +- pom.xml | 8 +- spring-cloud-etcd-config/pom.xml | 2 +- spring-cloud-etcd-core/pom.xml | 2 +- .../cloud/etcd/EtcdEndpoint.java | 9 +- spring-cloud-etcd-dependencies/pom.xml | 14 +- spring-cloud-etcd-discovery/pom.xml | 2 +- ...oServiceRegistrationAutoConfiguration.java | 47 ++++ .../etcd/discovery/EtcdDiscoveryClient.java | 34 +-- .../EtcdDiscoveryClientConfiguration.java | 15 +- .../cloud/etcd/discovery/EtcdLifecycle.java | 232 ------------------ .../etcd/discovery/EtcdRegistration.java | 164 +++++++++++++ .../cloud/etcd/discovery/EtcdServerList.java | 3 +- .../etcd/discovery/EtcdServiceRegistry.java | 99 ++++++++ .../EtcdServiceRegistryAutoConfiguration.java | 54 ++++ .../etcd/discovery/HeartbeatScheduler.java | 108 ++++++++ .../cloud/etcd/discovery/Service.java | 90 +++++++ .../main/resources/META-INF/spring.factories | 4 +- spring-cloud-etcd-sample/pom.xml | 4 +- .../etcd/sample/SampleEtcdApplication.java | 7 +- src/main/bash/travis_install_etcd.sh | 2 +- 22 files changed, 623 insertions(+), 283 deletions(-) create mode 100644 spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdAutoServiceRegistrationAutoConfiguration.java delete mode 100644 spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdLifecycle.java create mode 100644 spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdRegistration.java create mode 100644 spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdServiceRegistry.java create mode 100644 spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdServiceRegistryAutoConfiguration.java create mode 100644 spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/HeartbeatScheduler.java create mode 100644 spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/Service.java diff --git a/README.adoc b/README.adoc index 56fe059..5cdb080 100644 --- a/README.adoc +++ b/README.adoc @@ -39,7 +39,7 @@ See the https://coreos.com/etcd/[overview] for more information. == Building -:jdkversion: 1.7 +:jdkversion: 1.8 === Basic Compile and Test diff --git a/docs/pom.xml b/docs/pom.xml index e6dc302..7451a18 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -6,14 +6,14 @@ org.springframework.cloud spring-cloud-etcd - 1.0.0.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT .. spring-cloud-etcd-docs pom Spring Cloud Etcd Docs Spring Cloud Docs - 1.0.0.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT spring-cloud-etcd ${basedir}/.. diff --git a/pom.xml b/pom.xml index da9b3cc..ffb7888 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-etcd - 1.0.0.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT pom Spring Cloud Etcd Spring Cloud Etcd @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-build - 1.3.1.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT @@ -41,8 +41,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.7 - 1.7 + 1.8 + 1.8 diff --git a/spring-cloud-etcd-config/pom.xml b/spring-cloud-etcd-config/pom.xml index 0620ef0..a9431b5 100644 --- a/spring-cloud-etcd-config/pom.xml +++ b/spring-cloud-etcd-config/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-etcd - 1.0.0.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-etcd-core/pom.xml b/spring-cloud-etcd-core/pom.xml index 64e2c6c..4e55ba3 100644 --- a/spring-cloud-etcd-core/pom.xml +++ b/spring-cloud-etcd-core/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-etcd - 1.0.0.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT ../ diff --git a/spring-cloud-etcd-core/src/main/java/org/springframework/cloud/etcd/EtcdEndpoint.java b/spring-cloud-etcd-core/src/main/java/org/springframework/cloud/etcd/EtcdEndpoint.java index e713adc..8bec16e 100644 --- a/spring-cloud-etcd-core/src/main/java/org/springframework/cloud/etcd/EtcdEndpoint.java +++ b/spring-cloud-etcd-core/src/main/java/org/springframework/cloud/etcd/EtcdEndpoint.java @@ -18,23 +18,24 @@ import mousio.etcd4j.EtcdClient; -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.context.properties.ConfigurationProperties; /** * @author Spencer Gibb */ @ConfigurationProperties(prefix = "endpoints.etcd", ignoreUnknownFields = false) -public class EtcdEndpoint extends AbstractEndpoint { +@Endpoint(id="etcd") +public class EtcdEndpoint { private EtcdClient etcd; public EtcdEndpoint(EtcdClient etcd) { - super("etcd", false, true); this.etcd = etcd; } - @Override + @ReadOperation public Data invoke() { Data data = new Data(); data.setVersion(etcd.getVersion()); diff --git a/spring-cloud-etcd-dependencies/pom.xml b/spring-cloud-etcd-dependencies/pom.xml index a2eceeb..a97f655 100644 --- a/spring-cloud-etcd-dependencies/pom.xml +++ b/spring-cloud-etcd-dependencies/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-etcd-dependencies - 1.0.0.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT pom spring-cloud-etcd-dependencies Spring Cloud Etcd Dependencies @@ -13,16 +13,16 @@ org.springframework.cloud spring-cloud-dependencies-parent - 1.3.1.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT - 1.1.0.BUILD-SNAPSHOT - 1.1.0.BUILD-SNAPSHOT - 0.7.1 - 2.7.0 - 4.1.0.Beta5 + 2.0.0.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT + 0.7.5 + 2.15.0 + 4.1.22.Final 1.8 diff --git a/spring-cloud-etcd-discovery/pom.xml b/spring-cloud-etcd-discovery/pom.xml index d822144..8dc3007 100644 --- a/spring-cloud-etcd-discovery/pom.xml +++ b/spring-cloud-etcd-discovery/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-etcd - 1.0.0.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdAutoServiceRegistrationAutoConfiguration.java b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdAutoServiceRegistrationAutoConfiguration.java new file mode 100644 index 0000000..97fccf1 --- /dev/null +++ b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdAutoServiceRegistrationAutoConfiguration.java @@ -0,0 +1,47 @@ +/* + * Copyright 2017 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 + * + * 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 org.springframework.cloud.etcd.discovery; + +import javax.servlet.ServletContext; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; +import org.springframework.cloud.etcd.discovery.EtcdDiscoveryProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Venil Noronha + */ +@Configuration +@ConditionalOnBean(AutoServiceRegistrationProperties.class) +@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) +@AutoConfigureAfter(EtcdServiceRegistryAutoConfiguration.class) +public class EtcdAutoServiceRegistrationAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public EtcdRegistration etcdRegistration(EtcdDiscoveryProperties properties, ApplicationContext applicationContext, + ServletContext servletContext) { + return EtcdRegistration.registration(properties, applicationContext, servletContext); + } + +} diff --git a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdDiscoveryClient.java b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdDiscoveryClient.java index 87a862c..69bf881 100644 --- a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdDiscoveryClient.java +++ b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdDiscoveryClient.java @@ -17,6 +17,7 @@ package org.springframework.cloud.etcd.discovery; import mousio.etcd4j.EtcdClient; +import mousio.etcd4j.responses.EtcdAuthenticationException; import mousio.etcd4j.responses.EtcdException; import mousio.etcd4j.responses.EtcdKeysResponse; import org.springframework.beans.BeansException; @@ -34,20 +35,21 @@ /** * @author Spencer Gibb + * @author Venil Noronha */ public class EtcdDiscoveryClient implements DiscoveryClient, ApplicationContextAware { private final EtcdClient etcd; - private final EtcdLifecycle lifecycle; - private final EtcdDiscoveryProperties properties; + private final EtcdRegistration registration; + private ApplicationContext context; - public EtcdDiscoveryClient(EtcdClient etcd, EtcdLifecycle lifecycle, EtcdDiscoveryProperties properties) { + public EtcdDiscoveryClient(EtcdClient etcd, EtcdRegistration registration, EtcdDiscoveryProperties properties) { this.etcd = etcd; - this.lifecycle = lifecycle; + this.registration = registration; this.properties = properties; } @@ -58,27 +60,31 @@ public String description() { @Override public ServiceInstance getLocalServiceInstance() { - return new DefaultServiceInstance(lifecycle.getService().getAppName(), - properties.getHostname(), lifecycle.getConfiguredPort(), false); + return new DefaultServiceInstance(registration.getService().getAppName(), + properties.getHostname(), registration.getService().getPort(), false); } @Override public List getInstances(final String serviceId) { List instances = null; try { - EtcdKeysResponse response = etcd.getDir(lifecycle.getAppKey(serviceId)).send().get(); + EtcdKeysResponse response = etcd.getDir(getAppKey(registration.getService().getAppName())).send().get(); List nodes = response.node.nodes; instances = new ArrayList<>(); for (EtcdKeysResponse.EtcdNode node : nodes) { String[] parts = node.value.split(":"); instances.add(new DefaultServiceInstance(serviceId, parts[0], Integer.parseInt(parts[1]), false)); } - } catch (IOException | TimeoutException | EtcdException e) { + } catch (IOException | TimeoutException | EtcdException | EtcdAuthenticationException e) { ReflectionUtils.rethrowRuntimeException(e); } return instances; } + private String getAppKey(String appName) { + return properties.getDiscoveryPrefix() + "/" + appName; + } + @Override public List getServices() { List services = null; @@ -91,7 +97,7 @@ public List getServices() { serviceId = serviceId.substring(1); services.add(serviceId); } - } catch (IOException | EtcdException | TimeoutException e) { + } catch (IOException | EtcdException | TimeoutException | EtcdAuthenticationException e) { ReflectionUtils.rethrowRuntimeException(e); } return services; @@ -102,8 +108,8 @@ public void setApplicationContext(ApplicationContext context) throws BeansExcept this.context = context; } - public EtcdLifecycle getLifecycle() { - return lifecycle; + public EtcdRegistration getRegistration() { + return registration; } public EtcdDiscoveryProperties getProperties() { @@ -126,7 +132,7 @@ public boolean equals(Object o) { EtcdDiscoveryClient that = (EtcdDiscoveryClient) o; if (etcd != null ? !etcd.equals(that.etcd) : that.etcd != null) return false; - if (lifecycle != null ? !lifecycle.equals(that.lifecycle) : that.lifecycle != null) return false; + if (registration != null ? !registration.equals(that.registration) : that.registration != null) return false; if (properties != null ? !properties.equals(that.properties) : that.properties != null) return false; return context != null ? context.equals(that.context) : that.context == null; } @@ -134,7 +140,7 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = etcd != null ? etcd.hashCode() : 0; - result = 31 * result + (lifecycle != null ? lifecycle.hashCode() : 0); + result = 31 * result + (registration != null ? registration.hashCode() : 0); result = 31 * result + (properties != null ? properties.hashCode() : 0); result = 31 * result + (context != null ? context.hashCode() : 0); return result; @@ -142,6 +148,6 @@ public int hashCode() { @Override public String toString() { - return String.format("EtcdDiscoveryClient{etcd=%s, lifecycle=%s, properties=%s, context=%s}", etcd, lifecycle, properties, context); + return String.format("EtcdDiscoveryClient{etcd=%s, registration=%s, properties=%s, context=%s}", etcd, registration, properties, context); } } diff --git a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdDiscoveryClientConfiguration.java b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdDiscoveryClientConfiguration.java index 6fdfd33..a2b4c86 100644 --- a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdDiscoveryClientConfiguration.java +++ b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdDiscoveryClientConfiguration.java @@ -19,6 +19,8 @@ import mousio.etcd4j.EtcdClient; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -26,8 +28,10 @@ /** * @author Spencer Gibb + * @author Venil Noronha */ @Configuration +@ConditionalOnProperty(value = "spring.cloud.etcd.discovery.enabled", matchIfMissing = true) @EnableScheduling @EnableConfigurationProperties public class EtcdDiscoveryClientConfiguration { @@ -35,14 +39,15 @@ public class EtcdDiscoveryClientConfiguration { @Autowired private EtcdClient client; - @Bean - public EtcdLifecycle etcdLifecycle() { - return new EtcdLifecycle(client, etcdDiscoveryProperties()); + @ConditionalOnMissingBean + public HeartbeatScheduler heartbeatScheduler(EtcdDiscoveryProperties properties) { + return new HeartbeatScheduler(client, properties); } @Bean - public EtcdDiscoveryClient etcdDiscoveryClient() { - return new EtcdDiscoveryClient(client, etcdLifecycle(), etcdDiscoveryProperties()); + @ConditionalOnMissingBean + public EtcdDiscoveryClient etcdDiscoveryClient(EtcdDiscoveryProperties properties, EtcdRegistration registration) { + return new EtcdDiscoveryClient(client, registration, properties); } @Bean diff --git a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdLifecycle.java b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdLifecycle.java deleted file mode 100644 index cc12fd9..0000000 --- a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdLifecycle.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2013-2015 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 - * - * 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 org.springframework.cloud.etcd.discovery; - -import mousio.etcd4j.EtcdClient; -import mousio.etcd4j.responses.EtcdException; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.client.discovery.AbstractDiscoveryLifecycle; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; - -import java.io.IOException; -import java.util.concurrent.TimeoutException; - -/** - * @author Spencer Gibb - */ -public class EtcdLifecycle extends AbstractDiscoveryLifecycle { - - private static final Log log = LogFactory.getLog(EtcdLifecycle.class); - - private final EtcdClient etcd; - private final EtcdDiscoveryProperties props; - private final Service service = new Service(); - - public EtcdLifecycle(EtcdClient etcd, EtcdDiscoveryProperties props) { - this.etcd = etcd; - this.props = props; - } - - @Override - protected void register() { - Assert.notNull(service.getPort(), "service.port has not been set"); - - service.setAppName(getAppName()); - service.setId(getContext().getId()); - - register(service); - } - - @Scheduled(initialDelayString = "${spring.cloud.etcd.discovery.heartbeatInterval:25000}", fixedRateString = "${spring.cloud.etcd.discovery.heartbeatInterval:25000}") - protected void sendHeartbeat() { - register(); - } - - @Override - protected void registerManagement() { - Service management = new Service(); - management.setId(getManagementServiceId()); - management.setAppName(getManagementServiceName()); - management.setPort(getManagementPort()); - - register(management); - } - - protected void register(Service service) { - try { - log.info("Registering service with etcd: " + service); - String key = getServiceKey(service.appName, service.getId()); - //TODO: what should be serialized about the service? - String value = props.getHostname() + ":" + service.getPort(); - etcd.put(key, value).ttl(props.getTtl()).send().get(); - } catch (IOException | TimeoutException | EtcdException e) { - ReflectionUtils.rethrowRuntimeException(e); - } - } - - private String getServiceKey(String appName, String serviceId) { - return getAppKey(appName) + "/" + serviceId; - } - - public String getAppKey(String appName) { - return props.getDiscoveryPrefix() + "/" + appName; - } - - class Service { - String appName; - String id; - Integer port; - - public Service() { - } - - public Service(String appName, String id, Integer port) { - this.appName = appName; - this.id = id; - this.port = port; - } - - public String getAppName() { - return appName; - } - - public void setAppName(String appName) { - this.appName = appName; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public Integer getPort() { - return port; - } - - public void setPort(Integer port) { - this.port = port; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Service service = (Service) o; - - if (appName != null ? !appName.equals(service.appName) : service.appName != null) return false; - if (id != null ? !id.equals(service.id) : service.id != null) return false; - return port != null ? port.equals(service.port) : service.port == null; - } - - @Override - public int hashCode() { - int result = appName != null ? appName.hashCode() : 0; - result = 31 * result + (id != null ? id.hashCode() : 0); - result = 31 * result + (port != null ? port.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return String.format("Service{appName='%s', id='%s', port=%d}", appName, id, port); - } - } - - @Override - protected int getConfiguredPort() { - return service.getPort() == null? 0 : service.getPort(); - } - - @Override - protected void setConfiguredPort(int port) { - service.setPort(port); - } - - @Override - protected EtcdDiscoveryProperties getConfiguration() { - return props; - } - - @Override - protected void deregister() { - deregister(getAppName(), getContext().getId()); - } - - @Override - protected void deregisterManagement() { - deregister(getAppName(), getManagementServiceName()); - } - - private void deregister(String appName, String serviceId) { - try { - etcd.delete(getServiceKey(appName, serviceId)).send(); - } catch (IOException e) { - ReflectionUtils.rethrowRuntimeException(e); - } - } - - @Override - protected boolean isEnabled() { - return props.isEnabled(); - } - - public EtcdClient getEtcd() { - return etcd; - } - - public EtcdDiscoveryProperties getProps() { - return props; - } - - public Service getService() { - return service; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - EtcdLifecycle that = (EtcdLifecycle) o; - - if (etcd != null ? !etcd.equals(that.etcd) : that.etcd != null) return false; - if (props != null ? !props.equals(that.props) : that.props != null) return false; - if (service != null ? !service.equals(that.service) : that.service != null) return false; - - return true; - } - - @Override - public int hashCode() { - int result = etcd != null ? etcd.hashCode() : 0; - result = 31 * result + (props != null ? props.hashCode() : 0); - result = 31 * result + (service != null ? service.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return String.format("EtcdLifecycle{etcd=%s, props=%s, service=%s}", etcd, props, service); - } -} diff --git a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdRegistration.java b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdRegistration.java new file mode 100644 index 0000000..11dbcc5 --- /dev/null +++ b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdRegistration.java @@ -0,0 +1,164 @@ +/* + * Copyright 2017 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 + * + * 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 org.springframework.cloud.etcd.discovery; + +import javax.servlet.ServletContext; + +import org.springframework.cloud.client.discovery.ManagementServerPortUtils; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.context.ApplicationContext; +import org.springframework.core.env.Environment; + +/** + * @author Venil Noronha + */ +public class EtcdRegistration implements Registration { + + public static final char SEPARATOR = ':'; + public static final String MANAGEMENT = "management"; + + private final Service service; + private final EtcdDiscoveryProperties properties; + private final ApplicationContext context; + private String instanceId; + + public EtcdRegistration(Service service, EtcdDiscoveryProperties properties, ApplicationContext context) { + this.service = service; + this.properties = properties; + this.context = context; + + // cache instanceId, so on refresh this won't get recomputed + // this is a problem if ${random.value} is used + this.instanceId = EtcdRegistration.getServiceId(context); + } + + public String getInstanceId() { + return this.instanceId; + } + + public void initializePort(int knownPort) { + if (getService().getPort() == null) { + // not set by properties + getService().setPort(knownPort); + } + } + + public EtcdRegistration managementRegistration() { + return managementRegistration(this.properties, this.context); + } + + public static EtcdRegistration registration(EtcdDiscoveryProperties properties, ApplicationContext context, + ServletContext servletContext) { + Service service = new Service(); + service.setAppName(getAppName(properties, context.getEnvironment())); + service.setId(getServiceId(context)); + + return new EtcdRegistration(service, properties, context); + } + + public static EtcdRegistration managementRegistration(EtcdDiscoveryProperties properties, + ApplicationContext context) { + Service management = new Service(); + management.setAppName(getManagementServiceName(properties, context.getEnvironment())); + management.setId(getManagementServiceId(properties, context)); + management.setPort(getManagementPort(properties, context)); + + return new EtcdRegistration(management, properties, context); + } + + public String getServiceId() { + return this.service.getId(); + } + + public static String getServiceId(ApplicationContext context) { + return context.getId(); + } + + /** + * @return the app name, currently the spring.application.name property + */ + public static String getAppName(EtcdDiscoveryProperties properties, Environment environment) { + return environment.getProperty("spring.application.name", "application"); + } + + /** + * @return if the management service should be registered with the + * {@link ServiceRegistry} + */ + public static boolean shouldRegisterManagement(EtcdDiscoveryProperties properties, ApplicationContext context) { + return getManagementPort(properties, context) != null && ManagementServerPortUtils.isDifferent(context); + } + + /** + * @return the serviceId of the Management Service + */ + public static String getManagementServiceId(EtcdDiscoveryProperties properties, ApplicationContext context) { + return context.getId() + SEPARATOR + MANAGEMENT; + } + + /** + * @return the service name of the Management Service + */ + public static String getManagementServiceName(EtcdDiscoveryProperties properties, + Environment environment) { + return getAppName(properties, environment) + SEPARATOR + MANAGEMENT; + } + + /** + * @return the port of the Management Service + */ + public static Integer getManagementPort(EtcdDiscoveryProperties properties, ApplicationContext context) { + return ManagementServerPortUtils.getPort(context); + } + + public Service getService() { + return service; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((properties == null) ? 0 : properties.hashCode()); + result = prime * result + ((service == null) ? 0 : service.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EtcdRegistration other = (EtcdRegistration) obj; + if (properties == null) { + if (other.properties != null) + return false; + } else if (!properties.equals(other.properties)) + return false; + if (service == null) { + if (other.service != null) + return false; + } else if (!service.equals(other.service)) + return false; + return true; + } + +} diff --git a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdServerList.java b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdServerList.java index f90f50d..c066d25 100644 --- a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdServerList.java +++ b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdServerList.java @@ -19,6 +19,7 @@ import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractServerList; import mousio.etcd4j.EtcdClient; +import mousio.etcd4j.responses.EtcdAuthenticationException; import mousio.etcd4j.responses.EtcdException; import mousio.etcd4j.responses.EtcdKeysResponse; import mousio.etcd4j.responses.EtcdKeysResponse.EtcdNode; @@ -90,7 +91,7 @@ private List getServers() { EtcdServer server = new EtcdServer(appInfo[0], appInfo[1], strings[0], strings[1]); servers.add(server); } - } catch (IOException | TimeoutException | EtcdException e) { + } catch (IOException | TimeoutException | EtcdException | EtcdAuthenticationException e) { ReflectionUtils.rethrowRuntimeException(e); } diff --git a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdServiceRegistry.java b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdServiceRegistry.java new file mode 100644 index 0000000..0918124 --- /dev/null +++ b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdServiceRegistry.java @@ -0,0 +1,99 @@ +/* + * Copyright 2017 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 + * + * 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 org.springframework.cloud.etcd.discovery; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import mousio.etcd4j.responses.EtcdAuthenticationException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.util.ReflectionUtils; + +import mousio.etcd4j.EtcdClient; +import mousio.etcd4j.responses.EtcdException; + +/** + * @author Venil Noronha + */ +public class EtcdServiceRegistry implements ServiceRegistry { + + private static Log log = LogFactory.getLog(EtcdServiceRegistry.class); + + private final EtcdClient client; + private final EtcdDiscoveryProperties properties; + private final HeartbeatScheduler heartbeatScheduler; + + public EtcdServiceRegistry(EtcdClient client, EtcdDiscoveryProperties properties, HeartbeatScheduler heartbeatScheduler) { + this.client = client; + this.properties = properties; + this.heartbeatScheduler = heartbeatScheduler; + } + + @Override + public void register(EtcdRegistration reg) { + Service service = reg.getService(); + log.info("Registering service with etcd: " + service); + try { + heartbeatScheduler.register(service); + } + catch (IOException | EtcdException | TimeoutException | EtcdAuthenticationException e) { + ReflectionUtils.rethrowRuntimeException(e); + } + heartbeatScheduler.add(service); + } + + private String getServiceKey(String appName, String serviceId) { + return getAppKey(appName) + "/" + serviceId; + } + + public String getAppKey(String appName) { + return properties.getDiscoveryPrefix() + "/" + appName; + } + + @Override + public void deregister(EtcdRegistration reg) { + Service service = reg.getService(); + heartbeatScheduler.remove(service.getId()); + if (log.isInfoEnabled()) { + log.info("Deregistering service with etcd: " + service); + } + try { + client.delete(getServiceKey(service.getAppName(), service.getId())).send(); + } + catch (IOException e) { + ReflectionUtils.rethrowRuntimeException(e); + } + } + + @Override + public void close() { + + } + + @Override + public void setStatus(EtcdRegistration registration, String status) { + + } + + @Override + public Object getStatus(EtcdRegistration registration) { + return null; + } + +} diff --git a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdServiceRegistryAutoConfiguration.java b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdServiceRegistryAutoConfiguration.java new file mode 100644 index 0000000..410f1a8 --- /dev/null +++ b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/EtcdServiceRegistryAutoConfiguration.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 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 + * + * 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 org.springframework.cloud.etcd.discovery; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.etcd.discovery.EtcdDiscoveryProperties; +import org.springframework.cloud.etcd.discovery.HeartbeatScheduler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import mousio.etcd4j.EtcdClient; + +/** + * @author Venil Noronha + */ +@Configuration +@ConditionalOnProperty(value = "spring.cloud.service-registry.enabled", matchIfMissing = true) +public class EtcdServiceRegistryAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public EtcdServiceRegistry etcdServiceRegistry(EtcdClient etcdClient, EtcdDiscoveryProperties properties, + HeartbeatScheduler heartbeatScheduler) { + return new EtcdServiceRegistry(etcdClient, properties, heartbeatScheduler); + } + + @Bean + @ConditionalOnMissingBean + public HeartbeatScheduler heartbeatScheduler(EtcdClient etcdClient, EtcdDiscoveryProperties properties) { + return new HeartbeatScheduler(etcdClient, properties); + } + + @Bean + @ConditionalOnMissingBean + public EtcdDiscoveryProperties etcdDiscoveryProperties() { + return new EtcdDiscoveryProperties(); + } + +} diff --git a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/HeartbeatScheduler.java b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/HeartbeatScheduler.java new file mode 100644 index 0000000..cacd61a --- /dev/null +++ b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/HeartbeatScheduler.java @@ -0,0 +1,108 @@ +/* + * Copyright 2017 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 + * + * 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 org.springframework.cloud.etcd.discovery; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeoutException; + +import mousio.etcd4j.responses.EtcdAuthenticationException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; + +import mousio.etcd4j.EtcdClient; +import mousio.etcd4j.responses.EtcdException; + +/** + * @author Venil Noronha + */ +public class HeartbeatScheduler { + + private static Log log = LogFactory.getLog(HeartbeatScheduler.class); + + private final Map serviceHeartbeats = new ConcurrentHashMap<>(); + + private final TaskScheduler scheduler = new ConcurrentTaskScheduler(Executors.newSingleThreadScheduledExecutor()); + + private EtcdClient client; + + private EtcdDiscoveryProperties properties; + + public HeartbeatScheduler(EtcdClient client, EtcdDiscoveryProperties properties) { + this.client = client; + this.properties = properties; + } + + public void add(Service service) { + ScheduledFuture task = scheduler.scheduleAtFixedRate(new EtcdHeartbeatTask(service), + properties.getHeartbeatInterval()); + ScheduledFuture previousTask = serviceHeartbeats.put(service.getId(), task); + if (previousTask != null) { + previousTask.cancel(true); + } + } + + public void remove(String serviceId) { + ScheduledFuture task = serviceHeartbeats.get(serviceId); + if (task != null) { + task.cancel(true); + } + serviceHeartbeats.remove(serviceId); + } + + public void register(Service service) throws IOException, EtcdException, TimeoutException, EtcdAuthenticationException { + String key = getServiceKey(service.getAppName(), service.getId()); + // TODO: what should be serialized about the service? + String value = properties.getHostname() + ":" + service.getPort(); + client.put(key, value).ttl(properties.getTtl()).send().get(); + } + + private String getServiceKey(String appName, String serviceId) { + return getAppKey(appName) + "/" + serviceId; + } + + public String getAppKey(String appName) { + return properties.getDiscoveryPrefix() + "/" + appName; + } + + private class EtcdHeartbeatTask implements Runnable { + + private Service service; + + EtcdHeartbeatTask(Service service) { + this.service = service; + } + + @Override + public void run() { + log.debug("Sending etcd heartbeat for: " + service.getId()); + try { + register(service); + } + catch (IOException | EtcdException | TimeoutException | EtcdAuthenticationException e) { + log.error("Failed to send etcd heartbeat for: " + service.getId(), e); + } + } + + } + +} diff --git a/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/Service.java b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/Service.java new file mode 100644 index 0000000..3d88146 --- /dev/null +++ b/spring-cloud-etcd-discovery/src/main/java/org/springframework/cloud/etcd/discovery/Service.java @@ -0,0 +1,90 @@ +/* + * Copyright 2017 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 + * + * 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 org.springframework.cloud.etcd.discovery; + +/** + * @author Venil Noronha + */ +public class Service { + + private String appName; + private String id; + private Integer port; + + public Service() { + } + + public Service(String appName, String id, Integer port) { + this.appName = appName; + this.id = id; + this.port = port; + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Service service = (Service) o; + + if (appName != null ? !appName.equals(service.appName) : service.appName != null) + return false; + if (id != null ? !id.equals(service.id) : service.id != null) + return false; + return port != null ? port.equals(service.port) : service.port == null; + } + + @Override + public int hashCode() { + int result = appName != null ? appName.hashCode() : 0; + result = 31 * result + (id != null ? id.hashCode() : 0); + result = 31 * result + (port != null ? port.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return String.format("Service{appName='%s', id='%s', port=%d}", appName, id, port); + } + +} diff --git a/spring-cloud-etcd-discovery/src/main/resources/META-INF/spring.factories b/spring-cloud-etcd-discovery/src/main/resources/META-INF/spring.factories index daaf880..e3b7b23 100644 --- a/spring-cloud-etcd-discovery/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-etcd-discovery/src/main/resources/META-INF/spring.factories @@ -1,5 +1,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.etcd.discovery.RibbonEtcdAutoConfiguration +org.springframework.cloud.etcd.discovery.RibbonEtcdAutoConfiguration,\ +org.springframework.cloud.etcd.discovery.EtcdAutoServiceRegistrationAutoConfiguration,\ +org.springframework.cloud.etcd.discovery.EtcdServiceRegistryAutoConfiguration # Discovery Client Configuration org.springframework.cloud.client.discovery.EnableDiscoveryClient=\ diff --git a/spring-cloud-etcd-sample/pom.xml b/spring-cloud-etcd-sample/pom.xml index 9660c86..119b192 100644 --- a/spring-cloud-etcd-sample/pom.xml +++ b/spring-cloud-etcd-sample/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-etcd - 1.0.0.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT .. @@ -20,7 +20,7 @@ org.springframework.boot spring-boot-maven-plugin - 1.2.4.RELEASE + 2.0.0.RELEASE diff --git a/spring-cloud-etcd-sample/src/main/java/org/springframework/cloud/etcd/sample/SampleEtcdApplication.java b/spring-cloud-etcd-sample/src/main/java/org/springframework/cloud/etcd/sample/SampleEtcdApplication.java index 94d31b3..e6d2283 100644 --- a/spring-cloud-etcd-sample/src/main/java/org/springframework/cloud/etcd/sample/SampleEtcdApplication.java +++ b/spring-cloud-etcd-sample/src/main/java/org/springframework/cloud/etcd/sample/SampleEtcdApplication.java @@ -19,7 +19,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @@ -50,9 +49,6 @@ public class SampleEtcdApplication { @Autowired private Environment env; - @Autowired(required = false) - private RelaxedPropertyResolver resolver; - @RequestMapping("/me") public ServiceInstance me() { return discoveryClient.getLocalServiceInstance(); @@ -65,8 +61,7 @@ public ServiceInstance lb() { @RequestMapping("/myenv") public String env(@RequestParam("prop") String prop) { - String property = new RelaxedPropertyResolver(env).getProperty(prop, "Not Found"); - return property; + return env.getProperty(prop, "Not Found"); } @RequestMapping("/all") diff --git a/src/main/bash/travis_install_etcd.sh b/src/main/bash/travis_install_etcd.sh index 984e482..45889e8 100755 --- a/src/main/bash/travis_install_etcd.sh +++ b/src/main/bash/travis_install_etcd.sh @@ -3,7 +3,7 @@ #!/usr/bin/env bash ETCD_URL="https://github.com/coreos/etcd/releases/download" -ETCD_VER="v2.2.0" +ETCD_VER="v3.3.1" ETCD_ARC="linux-amd64" # cleanup