diff --git a/pom.xml b/pom.xml index fa7666be..73518c2c 100644 --- a/pom.xml +++ b/pom.xml @@ -39,17 +39,41 @@ - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.7 - 1.7 - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.7 + 1.7 + + + + org.codehaus.gmavenplus + gmavenplus-plugin + 1.4 + + + + testCompile + + + + + + maven-surefire-plugin + 2.6 + + false + + **/*Spec.java + + + + + @@ -161,6 +185,43 @@ 1.12.6 provided + + + org.spockframework + spock-core + ${spock.version} + test + + + org.spockframework + spock-spring + ${spock.version} + test + + + cglib + cglib-nodep + 3.1 + test + + + org.objenesis + objenesis + 2.1 + test + + + org.hamcrest + hamcrest-core + 1.3 + test + + + org.codehaus.groovy + groovy-all + 2.4.3 + test + @@ -171,6 +232,7 @@ 1.0.1.BUILD-SNAPSHOT 2.7.0 2.0-RC9 + 1.0-groovy-2.4 diff --git a/spring-cloud-zookeeper-bus/pom.xml b/spring-cloud-zookeeper-bus/pom.xml index 1b0fe0a6..df3ad807 100644 --- a/spring-cloud-zookeeper-bus/pom.xml +++ b/spring-cloud-zookeeper-bus/pom.xml @@ -16,6 +16,15 @@ .. + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.springframework.cloud diff --git a/spring-cloud-zookeeper-bus/src/main/java/org/springframework/cloud/zookeeper/bus/SimpleRemoteEvent.java b/spring-cloud-zookeeper-bus/src/main/java/org/springframework/cloud/zookeeper/bus/SimpleRemoteEvent.java index 3d567176..ddc8a1fc 100644 --- a/spring-cloud-zookeeper-bus/src/main/java/org/springframework/cloud/zookeeper/bus/SimpleRemoteEvent.java +++ b/spring-cloud-zookeeper-bus/src/main/java/org/springframework/cloud/zookeeper/bus/SimpleRemoteEvent.java @@ -11,17 +11,18 @@ import org.springframework.cloud.bus.event.RemoteApplicationEvent; @Data public class SimpleRemoteEvent extends RemoteApplicationEvent { - private String message; + private String message; - private SimpleRemoteEvent(){} + private SimpleRemoteEvent() { + } - public SimpleRemoteEvent(Object source, String originService, String destinationService, String message) { - super(source, originService, destinationService); - this.message = message; - } + public SimpleRemoteEvent(Object source, String originService, String destinationService, String message) { + super(source, originService, destinationService); + this.message = message; + } - public SimpleRemoteEvent(Object source, String originService, String message) { - super(source, originService); - this.message = message; - } + public SimpleRemoteEvent(Object source, String originService, String message) { + super(source, originService); + this.message = message; + } } diff --git a/spring-cloud-zookeeper-config/pom.xml b/spring-cloud-zookeeper-config/pom.xml index 3ba979e0..a32f8a9a 100644 --- a/spring-cloud-zookeeper-config/pom.xml +++ b/spring-cloud-zookeeper-config/pom.xml @@ -16,6 +16,15 @@ .. + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.springframework.cloud diff --git a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySource.java b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySource.java index 9ab7a72b..0dfd46ce 100644 --- a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySource.java +++ b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySource.java @@ -42,16 +42,14 @@ public class ZookeeperPropertySource extends EnumerablePropertySource entry : children.entrySet()) { ChildData child = entry.getValue(); - if (child.getData()==null || child.getData().length == 0) { + if (child.getData() == null || child.getData().length == 0) { findKeys(keys, child.getPath()); - } - else { + } else { keys.add(child.getPath().replace(context + "/", "").replace('/', '.')); } } diff --git a/spring-cloud-zookeeper-core/pom.xml b/spring-cloud-zookeeper-core/pom.xml index 1f2f3119..0a973323 100644 --- a/spring-cloud-zookeeper-core/pom.xml +++ b/spring-cloud-zookeeper-core/pom.xml @@ -16,6 +16,19 @@ .. + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.codehaus.gmavenplus + gmavenplus-plugin + + + + org.springframework.boot @@ -49,7 +62,37 @@ spring-boot-starter-test test - + + + org.spockframework + spock-core + test + + + org.spockframework + spock-spring + test + + + cglib + cglib-nodep + test + + + org.objenesis + objenesis + test + + + org.hamcrest + hamcrest-core + test + + + org.codehaus.groovy + groovy-all + test + diff --git a/spring-cloud-zookeeper-core/src/main/java/org/springframework/cloud/zookeeper/ZookeeperAutoConfiguration.java b/spring-cloud-zookeeper-core/src/main/java/org/springframework/cloud/zookeeper/ZookeeperAutoConfiguration.java index c2d8596e..10140cf1 100644 --- a/spring-cloud-zookeeper-core/src/main/java/org/springframework/cloud/zookeeper/ZookeeperAutoConfiguration.java +++ b/spring-cloud-zookeeper-core/src/main/java/org/springframework/cloud/zookeeper/ZookeeperAutoConfiguration.java @@ -1,21 +1,26 @@ package org.springframework.cloud.zookeeper; -import javax.annotation.PreDestroy; - +import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; +import org.springframework.beans.factory.annotation.Autowired; 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; +import javax.annotation.PreDestroy; + /** * @author Spencer Gibb */ @Configuration @EnableConfigurationProperties public class ZookeeperAutoConfiguration { + + private @Autowired RetryPolicy retryPolicy; + @Bean @ConditionalOnMissingBean public ZookeeperProperties zookeeperProperties() { @@ -26,9 +31,7 @@ public class ZookeeperAutoConfiguration { @ConditionalOnMissingBean public CuratorFramework curatorFramework() { CuratorFramework curator = CuratorFrameworkFactory.builder() - // TODO: configurable retry policy - .retryPolicy(new ExponentialBackoffRetry(1000, 3)) - // .retryPolicy(new RetryOneTime(100)) + .retryPolicy(retryPolicy) // TODO: support ensembleProvider via ExhibitorEnsembleProvider // .ensembleProvider(new ExhibitorEnsembleProvider()) .connectString(zookeeperProperties().getConnectString()).build(); @@ -41,6 +44,14 @@ public class ZookeeperAutoConfiguration { curatorFramework().close(); } + @Bean + @ConditionalOnMissingBean + public RetryPolicy exponentialBackoffRetry() { + return new ExponentialBackoffRetry(zookeeperProperties().getBaseSleepTimeMs(), + zookeeperProperties().getMaxRetries(), + zookeeperProperties().getMaxSleepMs()); + } + @Bean @ConditionalOnMissingBean public ZookeeperEndpoint zookeeperEndpoint() { diff --git a/spring-cloud-zookeeper-core/src/main/java/org/springframework/cloud/zookeeper/ZookeeperProperties.java b/spring-cloud-zookeeper-core/src/main/java/org/springframework/cloud/zookeeper/ZookeeperProperties.java index 3010dfbe..150a2ee6 100644 --- a/spring-cloud-zookeeper-core/src/main/java/org/springframework/cloud/zookeeper/ZookeeperProperties.java +++ b/spring-cloud-zookeeper-core/src/main/java/org/springframework/cloud/zookeeper/ZookeeperProperties.java @@ -1,11 +1,10 @@ package org.springframework.cloud.zookeeper; -import javax.validation.constraints.NotNull; - import lombok.Data; - import org.springframework.boot.context.properties.ConfigurationProperties; +import javax.validation.constraints.NotNull; + /** * @author Spencer Gibb */ @@ -16,4 +15,20 @@ public class ZookeeperProperties { private String connectString = "localhost:2181"; private boolean enabled = true; + + /** + * @param baseSleepTimeMs initial amount of time to wait between retries + */ + private Integer baseSleepTimeMs = 50; + + /** + * @param maxRetries max number of times to retry + */ + private Integer maxRetries = 50; + + /** + * @param maxSleepMs max time in ms to sleep on each retry + */ + private Integer maxSleepMs = 500; + } diff --git a/spring-cloud-zookeeper-core/src/test/groovy/org/springframework/cloud/zookeeper/ZookeeperAutoConfigurationSpec.groovy b/spring-cloud-zookeeper-core/src/test/groovy/org/springframework/cloud/zookeeper/ZookeeperAutoConfigurationSpec.groovy new file mode 100644 index 00000000..e210937d --- /dev/null +++ b/spring-cloud-zookeeper-core/src/test/groovy/org/springframework/cloud/zookeeper/ZookeeperAutoConfigurationSpec.groovy @@ -0,0 +1,37 @@ +package org.springframework.cloud.zookeeper + +import org.apache.curator.framework.CuratorFramework +import org.apache.curator.test.TestingServer +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Bean +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification + +/** + * @author Spencer Gibb + */ +@ContextConfiguration(classes = [ TestConfig, ZookeeperAutoConfiguration ]) +class ZookeeperAutoConfigurationSpec extends Specification { + + @Autowired(required = false) + CuratorFramework curator + + def 'should successfully inject Curator as a Spring bean'() { + expect: + curator != null + } + + static class TestConfig { + @Bean + ZookeeperProperties zookeeperProperties() throws Exception { + ZookeeperProperties properties = new ZookeeperProperties() + properties.connectString = testingServer().connectString + return properties + } + + @Bean + TestingServer testingServer() throws Exception { + return new TestingServer() + } + } +} diff --git a/spring-cloud-zookeeper-core/src/test/java/org/springframework/cloud/zookeeper/ZookeeperAutoConfigurationTests.java b/spring-cloud-zookeeper-core/src/test/java/org/springframework/cloud/zookeeper/ZookeeperAutoConfigurationTests.java deleted file mode 100644 index a7f754cf..00000000 --- a/spring-cloud-zookeeper-core/src/test/java/org/springframework/cloud/zookeeper/ZookeeperAutoConfigurationTests.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.springframework.cloud.zookeeper; - -import static org.junit.Assert.assertNotNull; - -import org.apache.curator.framework.CuratorFramework; -import org.apache.curator.test.TestingServer; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -/** - * @author Spencer Gibb - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = { ZookeeperAutoConfigurationTests.TestConfig.class, - ZookeeperAutoConfiguration.class }) -public class ZookeeperAutoConfigurationTests { - - @Autowired(required = false) - CuratorFramework curator; - - @Test - public void testZookeeperFramework() { - assertNotNull("curator is null", curator); - } - - static class TestConfig { - @Bean - public ZookeeperProperties zookeeperProperties() throws Exception { - ZookeeperProperties properties = new ZookeeperProperties(); - properties.setConnectString(testingServer().getConnectString()); - return properties; - } - - @Bean - public TestingServer testingServer() throws Exception { - return new TestingServer(); - } - } -} diff --git a/spring-cloud-zookeeper-discovery/pom.xml b/spring-cloud-zookeeper-discovery/pom.xml index f11b53a2..33c1dfc1 100644 --- a/spring-cloud-zookeeper-discovery/pom.xml +++ b/spring-cloud-zookeeper-discovery/pom.xml @@ -16,6 +16,22 @@ .. + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.codehaus.gmavenplus + gmavenplus-plugin + + + maven-surefire-plugin + + + + org.springframework.cloud @@ -52,6 +68,48 @@ spring-boot-starter-test test + + org.apache.curator + curator-test + test + + + + org.spockframework + spock-core + test + + + org.spockframework + spock-spring + test + + + cglib + cglib-nodep + test + + + org.objenesis + objenesis + test + + + org.hamcrest + hamcrest-core + test + + + org.codehaus.groovy + groovy-all + test + + + com.github.tomakehurst + wiremock + test + 1.53 + diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/AddressProviderConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/AddressProviderConfiguration.java new file mode 100644 index 00000000..56efb673 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/AddressProviderConfiguration.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-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.zookeeper.discovery; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; + +/** + * Configuration that registers a bean related to microservice's address and port providing. + * + * @see MicroserviceAddressProvider + * + * @author Marcin Grzejszczak, 4financeIT + * @author Tomasz Dziurko, 4financeIT + */ +@Configuration +public class AddressProviderConfiguration { + + @Autowired + private Environment environment; + + @Bean + MicroserviceAddressProvider microserviceAddressProvider() { + String microserviceHost = environment.getProperty("microservice.host", getIpAddress()); + Integer microservicePort = Integer.valueOf(environment.getProperty("server.port", "8080")); + return new MicroserviceAddressProvider(microserviceHost, microservicePort); + } + + /** + * Return a non loopback IPv4 address for the machine running this process. + * If the machine has multiple network interfaces, the IP address for the + * first interface returned by {@link java.net.NetworkInterface#getNetworkInterfaces} + * is returned. + * + * @return non loopback IPv4 address for the machine running this process + * @see java.net.NetworkInterface#getNetworkInterfaces + * @see java.net.NetworkInterface#getInetAddresses + */ + public static String getIpAddress() { + try { + for (Enumeration enumNic = NetworkInterface.getNetworkInterfaces(); + enumNic.hasMoreElements(); ) { + NetworkInterface ifc = enumNic.nextElement(); + if (ifc.isUp()) { + for (Enumeration enumAddr = ifc.getInetAddresses(); + enumAddr.hasMoreElements(); ) { + InetAddress address = enumAddr.nextElement(); + if (address instanceof Inet4Address && !address.isLoopbackAddress()) { + return address.getHostAddress(); + } + } + } + } + } catch (IOException e) { + // ignore + } + return "unknown"; + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/MicroserviceAddressProvider.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/MicroserviceAddressProvider.java new file mode 100644 index 00000000..897bbc78 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/MicroserviceAddressProvider.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-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.zookeeper.discovery; + +/** + * Holder for microservice's host and port + * + * @author Marcin Grzejszczak, 4financeIT + * @author Adam Chudzik, 4financeIT + * + */ +public class MicroserviceAddressProvider { + private final String host; + private final int port; + + public MicroserviceAddressProvider(String microserviceHost, int microservicePort) { + this.host = microserviceHost; + this.port = microservicePort; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } +} \ No newline at end of file diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/RibbonZookeeperAutoConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/RibbonZookeeperAutoConfiguration.java index f45f1c19..0861194e 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/RibbonZookeeperAutoConfiguration.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/RibbonZookeeperAutoConfiguration.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryClient.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryClient.java index 30249953..c32efb6e 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryClient.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryClient.java @@ -1,22 +1,23 @@ package org.springframework.cloud.zookeeper.discovery; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - import lombok.SneakyThrows; - import org.apache.curator.x.discovery.ServiceDiscovery; import org.apache.curator.x.discovery.ServiceInstance; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies; import org.springframework.context.ApplicationContext; -import static org.springframework.util.ReflectionUtils.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static org.springframework.util.ReflectionUtils.rethrowRuntimeException; /** * @author Spencer Gibb + * @author Marcin Grzejszczak, 4financeIT */ public class ZookeeperDiscoveryClient implements DiscoveryClient { @@ -29,6 +30,9 @@ public class ZookeeperDiscoveryClient implements DiscoveryClient { @Autowired ServiceDiscovery discovery; + @Autowired(required = false) + ZookeeperDependencies zookeeperDependencies; + @Override public String description() { return "Spring Cloud Zookeeper Discovery Client"; @@ -59,18 +63,26 @@ public class ZookeeperDiscoveryClient implements DiscoveryClient { @SneakyThrows public List getInstances( final String serviceId) { + String serviceIdToQuery = getServiceIdToQuery(serviceId); Collection> zkInstances = discovery - .queryForInstances(serviceId); + .queryForInstances(serviceIdToQuery); ArrayList instances = new ArrayList<>(); for (ServiceInstance instance : zkInstances) { - instances.add(createServiceInstance(serviceId, instance)); + instances.add(createServiceInstance(serviceIdToQuery, instance)); } return instances; } + private String getServiceIdToQuery(String serviceId) { + if (zookeeperDependencies != null && zookeeperDependencies.hasDependencies()) { + return zookeeperDependencies.getPathForAlias(serviceId); + } + return serviceId; + } + @Override public List getServices() { ArrayList services = null; diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryClientConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryClientConfiguration.java index 08e35d15..e4a82e53 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryClientConfiguration.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryClientConfiguration.java @@ -8,6 +8,7 @@ import org.apache.curator.x.discovery.UriSpec; import org.apache.curator.x.discovery.details.InstanceSerializer; import org.apache.curator.x.discovery.details.JsonInstanceSerializer; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -19,6 +20,7 @@ import org.springframework.core.env.Environment; * @author Spencer Gibb */ @Configuration +@Import(AddressProviderConfiguration.class) @EnableConfigurationProperties public class ZookeeperDiscoveryClientConfiguration { @Autowired @@ -40,13 +42,14 @@ public class ZookeeperDiscoveryClientConfiguration { } @Bean - public ServiceInstance serviceInstance() throws Exception { + @ConditionalOnMissingBean + public ServiceInstance serviceInstance(MicroserviceAddressProvider microserviceAddressProvider) throws Exception { Environment environment = context.getEnvironment(); - Integer port = new Integer(environment.getProperty("server.port", "8080")); UriSpec uriSpec = new UriSpec(zookeeperDiscoveryProperties().getUriSpec()); return ServiceInstance. builder() .name(environment.getProperty("spring.application.name")) - .payload(new ZookeeperInstance(context.getId())).port(port) + .payload(new ZookeeperInstance(context.getId())).port(microserviceAddressProvider.getPort()) + .address(microserviceAddressProvider.getHost()) .uriSpec(uriSpec).build(); } @@ -56,15 +59,17 @@ public class ZookeeperDiscoveryClientConfiguration { } @Bean - public ServiceDiscovery serviceDiscovery(CuratorFramework curator) + @ConditionalOnMissingBean + public ServiceDiscovery serviceDiscovery(CuratorFramework curator, ServiceInstance serviceInstance) throws Exception { return ServiceDiscoveryBuilder.builder(ZookeeperInstance.class).client(curator) .basePath(zookeeperDiscoveryProperties().getRoot()) - .serializer(instanceSerializer()).thisInstance(serviceInstance()).build(); + .serializer(instanceSerializer()).thisInstance(serviceInstance).build(); } @Bean public ZookeeperDiscoveryHealthIndicator zookeeperDiscoveryHealthIndicator() { return new ZookeeperDiscoveryHealthIndicator(); } + } diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryProperties.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryProperties.java index f79c2216..8f466386 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryProperties.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryProperties.java @@ -15,4 +15,9 @@ public class ZookeeperDiscoveryProperties { private String root = "/services"; private String uriSpec = "{scheme}://{address}:{port}"; + + /** + * @param realm allows you to register a service under specified realm + */ + private String realm; } diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperRibbonClientConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperRibbonClientConfiguration.java index df398df8..8f8e78fb 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperRibbonClientConfiguration.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperRibbonClientConfiguration.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -16,23 +16,26 @@ package org.springframework.cloud.zookeeper.discovery; -import static com.netflix.client.config.CommonClientConfigKey.DeploymentContextBasedVipAddresses; -import static com.netflix.client.config.CommonClientConfigKey.EnableZoneAffinity; - -import javax.annotation.PostConstruct; - -import org.apache.curator.x.discovery.ServiceDiscovery; -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 com.netflix.client.config.IClientConfig; import com.netflix.config.ConfigurationManager; import com.netflix.config.DynamicPropertyFactory; import com.netflix.config.DynamicStringProperty; import com.netflix.loadbalancer.ServerList; +import org.apache.curator.x.discovery.ServiceDiscovery; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.zookeeper.discovery.dependency.DependenciesPassedCondition; +import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +import static com.netflix.client.config.CommonClientConfigKey.DeploymentContextBasedVipAddresses; +import static com.netflix.client.config.CommonClientConfigKey.EnableZoneAffinity; /** * Preprocessor that configures defaults for eureka-discovered ribbon clients. Such as: @@ -41,6 +44,7 @@ import com.netflix.loadbalancer.ServerList; * * @author Spencer Gibb * @author Dave Syer + * @author Marcin Grzejszczak, 4financeIT */ @Configuration public class ZookeeperRibbonClientConfiguration { @@ -54,6 +58,16 @@ public class ZookeeperRibbonClientConfiguration { public ZookeeperRibbonClientConfiguration() { } + @Bean + @ConditionalOnMissingBean + @Conditional(DependenciesPassedCondition.class) + @ConditionalOnProperty(value = "zookeeper.dependencies.enabled", matchIfMissing = true) + public ServerList ribbonServerListFromDependencies(IClientConfig config, ZookeeperDependencies zookeeperDependencies) { + ZookeeperServerList serverList = new ZookeeperServerList(serviceDiscovery); + serverList.initFromDependencies(config, zookeeperDependencies); + return serverList; + } + @Bean @ConditionalOnMissingBean public ServerList ribbonServerList(IClientConfig config) { diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperServerList.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperServerList.java index bb0fb9cb..d44c7730 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperServerList.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ZookeeperServerList.java @@ -1,20 +1,21 @@ package org.springframework.cloud.zookeeper.discovery; +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.AbstractServerList; +import org.apache.curator.x.discovery.ServiceDiscovery; +import org.apache.curator.x.discovery.ServiceInstance; +import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import org.apache.curator.x.discovery.ServiceDiscovery; -import org.apache.curator.x.discovery.ServiceInstance; - -import com.netflix.client.config.IClientConfig; -import com.netflix.loadbalancer.AbstractServerList; - import static org.springframework.util.ReflectionUtils.rethrowRuntimeException; /** * @author Spencer Gibb + * @author Marcin Grzejszczak, 4financeIT */ public class ZookeeperServerList extends AbstractServerList { @@ -30,6 +31,10 @@ public class ZookeeperServerList extends AbstractServerList { this.serviceId = clientConfig.getClientName(); } + public void initFromDependencies(IClientConfig clientConfig, ZookeeperDependencies zookeeperDependencies) { + this.serviceId = zookeeperDependencies.getPathForAlias(clientConfig.getClientName()); + } + @Override public List getInitialListOfServers() { return getServers(); diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependenciesPassedCondition.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependenciesPassedCondition.java new file mode 100644 index 00000000..f4fc624d --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependenciesPassedCondition.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-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.zookeeper.discovery.dependency; + +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.bind.RelaxedPropertyResolver; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +import java.util.Map; + +/** + * @author Marcin Grzejszczak, 4financeIT + */ +public class DependenciesPassedCondition extends SpringBootCondition { + + private static final String ZOOKEEPER_DEPENDENCIES_PROP = "zookeeper.dependencies"; + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + Map subProperties = new RelaxedPropertyResolver(context.getEnvironment()).getSubProperties(ZOOKEEPER_DEPENDENCIES_PROP); + return subProperties.isEmpty() ? + ConditionOutcome.noMatch("No dependencies have been passed for the service") : + ConditionOutcome.match(); + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/LoadBalancerType.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/LoadBalancerType.java new file mode 100644 index 00000000..139f3e80 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/LoadBalancerType.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-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.zookeeper.discovery.dependency; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.Predicate; + +import java.util.Arrays; + +import static org.apache.commons.lang.StringUtils.EMPTY; +import static org.apache.commons.lang.StringUtils.defaultIfEmpty; + +/** + * @author Marcin Grzejszczak, 4financeIT + */ +public enum LoadBalancerType { + STICKY, RANDOM, ROUND_ROBIN; + + public static LoadBalancerType fromName(final String strategyName) { + LoadBalancerType loadBalancerType = (LoadBalancerType) CollectionUtils.find(Arrays.asList(values()), new Predicate() { + @Override + public boolean evaluate(Object o) { + LoadBalancerType input = (LoadBalancerType) o; + return input.name().equals(defaultIfEmpty(strategyName, EMPTY).toUpperCase()); + } + }); + if (loadBalancerType == null) { + return ROUND_ROBIN; + } + return loadBalancerType; + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependencies.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependencies.java new file mode 100644 index 00000000..763a4991 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependencies.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-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.zookeeper.discovery.dependency; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.StringUtils; + +import javax.annotation.PostConstruct; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author Marcin Grzejszczak, 4financeIT + */ +@Data +@ConfigurationProperties("zookeeper") +public class ZookeeperDependencies { + + private String prefix = ""; + + private Map dependencies = new LinkedHashMap<>(); + + @PostConstruct + public void init() { + for (Map.Entry entry : this.dependencies.entrySet()) { + ZookeeperDependency value = entry.getValue(); + if (StringUtils.hasText(prefix)) { + value.path = prefix + value.path; + } + } + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class ZookeeperDependency { + + private String id; + + private String path; + + private LoadBalancerType loadBalancerType; + + private String contentTypeTemplate; + + private String version; + + private Map headers; + + private boolean required; + } + + public Collection getDependencyConfigurations() { + return dependencies.values(); + } + + public boolean hasDependencies() { + return !dependencies.isEmpty(); + } + + public String getPathForAlias(final String alias) { + for (Map.Entry zookeeperDependencyEntry : dependencies.entrySet()) { + if (zookeeperDependencyEntry.getKey().equals(alias)) { + return zookeeperDependencyEntry.getValue().getPath(); + } + } + return ""; + } +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependenciesAutoConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependenciesAutoConfiguration.java new file mode 100644 index 00000000..efeec4d7 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependenciesAutoConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-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.zookeeper.discovery.dependency; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.zookeeper.ZookeeperAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +/** + * Provides autoconfiguration for Zookeeper dependency set up in properties. + * + * @see ZookeeperDependencies + * + * @author Marcin Grzejszczak, 4financeIT + * + */ +@Configuration +@EnableConfigurationProperties +@Conditional(DependenciesPassedCondition.class) +@ConditionalOnProperty(value = "zookeeper.dependencies.enabled", matchIfMissing = true) +@AutoConfigureAfter(ZookeeperAutoConfiguration.class) +public class ZookeeperDependenciesAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public ZookeeperDependencies zookeeperDependencies() { + return new ZookeeperDependencies(); + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DefaultDependencyWatcher.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DefaultDependencyWatcher.java new file mode 100755 index 00000000..d3ef8f24 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DefaultDependencyWatcher.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-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.zookeeper.discovery.watcher; + +import org.apache.curator.x.discovery.ServiceCache; +import org.apache.curator.x.discovery.ServiceDiscovery; +import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies; +import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies.ZookeeperDependency; +import org.springframework.cloud.zookeeper.discovery.watcher.presence.DependencyPresenceOnStartupVerifier; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Marcin Grzejszczak, 4financeIT + * @author Michal Chmielarz, 4financeIT + */ +public class DefaultDependencyWatcher implements DependencyRegistrationHookProvider { + + private final ServiceDiscovery serviceDiscovery; + private final Map dependencyRegistry = new HashMap<>(); + private final List listeners; + private final DependencyPresenceOnStartupVerifier dependencyPresenceOnStartupVerifier; + private final ZookeeperDependencies zookeeperDependencies; + + public DefaultDependencyWatcher(ServiceDiscovery serviceDiscovery, + DependencyPresenceOnStartupVerifier dependencyPresenceOnStartupVerifier, + List dependencyWatcherListeners, + ZookeeperDependencies zookeeperDependencies) { + this.serviceDiscovery = serviceDiscovery; + this.dependencyPresenceOnStartupVerifier = dependencyPresenceOnStartupVerifier; + this.listeners = dependencyWatcherListeners; + this.zookeeperDependencies = zookeeperDependencies; + } + + @Override + @SuppressWarnings("unchecked") + public void registerDependencyRegistrationHooks() throws Exception { + for (ZookeeperDependency zookeeperDependency : zookeeperDependencies.getDependencyConfigurations()) { + String dependencyPath = zookeeperDependency.getPath(); + ServiceCache serviceCache = serviceDiscovery.serviceCacheBuilder().name(dependencyPath).build(); + serviceCache.start(); + dependencyPresenceOnStartupVerifier.verifyDependencyPresence(dependencyPath, serviceCache, zookeeperDependency.isRequired()); + dependencyRegistry.put(dependencyPath, serviceCache); + serviceCache.addListener(new DependencyStateChangeListenerRegistry(listeners, dependencyPath, serviceCache)); + } + } + + @Override + public void clearDependencyRegistrationHooks() throws IOException { + for (ServiceCache cache : dependencyRegistry.values()) { + cache.close(); + } + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DependencyRegistrationHookProvider.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DependencyRegistrationHookProvider.java new file mode 100644 index 00000000..1b60fbc0 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DependencyRegistrationHookProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-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.zookeeper.discovery.watcher; + +import java.io.IOException; + +/** + * @author listeners; + private final String dependencyName; + private final ServiceCache serviceCache; + + public DependencyStateChangeListenerRegistry(List listeners, String dependencyName, ServiceCache serviceCache) { + this.listeners = listeners; + this.dependencyName = dependencyName; + this.serviceCache = serviceCache; + } + + @Override + public void cacheChanged() { + DependencyState state = serviceCache.getInstances().isEmpty() ? DependencyState.DISCONNECTED : DependencyState.CONNECTED; + logCurrentState(state); + informListeners(state); + } + + private void logCurrentState(DependencyState dependencyState) { + log.info("Service cache state change for '{}' instances, current service state: {}", dependencyName, dependencyState); + } + + private void informListeners(DependencyState state) { + for (DependencyWatcherListener listener : listeners) { + listener.stateChanged(dependencyName, state); + } + } + + @Override + public void stateChanged(CuratorFramework client, ConnectionState newState) { + // todo do something or ignore for what is worth + } +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DependencyWatcher.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DependencyWatcher.java new file mode 100644 index 00000000..6284cd9e --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DependencyWatcher.java @@ -0,0 +1,34 @@ +package org.springframework.cloud.zookeeper.discovery.watcher; + +import java.io.IOException; + +public interface DependencyWatcher { + + /** + * Register hooks upon dependencies registration + * + * @throws Exception + */ + void registerDependencyRegistrationHooks() throws Exception; + + /** + * Unregister hooks upon dependencies registration + * + * @throws IOException + */ + void clearDependencyRegistrationHooks() throws IOException; + + /** + * Register a listener for a dependency + * + * @param listener + */ + void registerDependencyStateChangeListener(DependencyWatcherListener listener); + + /** + * Unregister a listener for a dependency + * + * @param listener + */ + void clearDependencyStateChangeListener(DependencyWatcherListener listener); +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DependencyWatcherAutoConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DependencyWatcherAutoConfiguration.java new file mode 100644 index 00000000..a0b62a1c --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DependencyWatcherAutoConfiguration.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-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.zookeeper.discovery.watcher; + +import org.apache.curator.x.discovery.ServiceDiscovery; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.zookeeper.discovery.dependency.DependenciesPassedCondition; +import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies; +import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependenciesAutoConfiguration; +import org.springframework.cloud.zookeeper.discovery.watcher.presence.DefaultDependencyPresenceOnStartupVerifier; +import org.springframework.cloud.zookeeper.discovery.watcher.presence.DependencyPresenceOnStartupVerifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +/** + * Provides hooks for observing dependency lifecycle in Zookeeper. + * Needs custom dependencies to be set in order to work. + * + * @see ZookeeperDependencies + * + * @author Marcin Grzejszczak, 4financeIT + */ +@Configuration +@EnableConfigurationProperties +@Conditional(DependenciesPassedCondition.class) +@ConditionalOnProperty(value = "zookeeper.dependencies.enabled", matchIfMissing = true) +@AutoConfigureAfter(ZookeeperDependenciesAutoConfiguration.class) +public class DependencyWatcherAutoConfiguration { + + @Autowired(required = false) + private List dependencyWatcherListeners = new ArrayList<>(); + + @Bean + @ConditionalOnMissingBean + public DependencyPresenceOnStartupVerifier dependencyPresenceOnStartupVerifier() { + return new DefaultDependencyPresenceOnStartupVerifier(); + } + + @Bean(initMethod = "registerDependencyRegistrationHooks", destroyMethod = "clearDependencyRegistrationHooks") + @ConditionalOnMissingBean + public DependencyRegistrationHookProvider dependencyWatcher(ServiceDiscovery serviceDiscovery, + DependencyPresenceOnStartupVerifier dependencyPresenceOnStartupVerifier, + ZookeeperDependencies zookeeperDependencies) { + return new DefaultDependencyWatcher(serviceDiscovery, + dependencyPresenceOnStartupVerifier, + dependencyWatcherListeners, + zookeeperDependencies); + } +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DependencyWatcherListener.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DependencyWatcherListener.java new file mode 100755 index 00000000..a24ae717 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/DependencyWatcherListener.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-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.zookeeper.discovery.watcher; + +/** + * Performs logic upon change of state of a dependency {@see DependencyState} + * in the service discovery system. + * + * @author serviceInstances) { + if (serviceInstances.isEmpty()) { + throw new NoInstancesRunningException(dependencyName); + } + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/presence/LogMissingDependencyChecker.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/presence/LogMissingDependencyChecker.java new file mode 100755 index 00000000..1808fbcd --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/presence/LogMissingDependencyChecker.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-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.zookeeper.discovery.watcher.presence; + +import org.apache.curator.x.discovery.ServiceInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.invoke.MethodHandles; +import java.util.List; + +/** + * @author Marcin Grzejszczak, 4financeIT + * @author Tomasz Dziurko, 4financeIT + */ +public class LogMissingDependencyChecker implements PresenceChecker { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + @Override + public void checkPresence(String dependencyName, List serviceInstances) { + if (serviceInstances.isEmpty()) { + log.warn("Microservice dependency with name [" + dependencyName + "] is missing."); + } + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/presence/NoInstancesRunningException.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/presence/NoInstancesRunningException.java new file mode 100755 index 00000000..5810b803 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/presence/NoInstancesRunningException.java @@ -0,0 +1,25 @@ +/* + * Copyright 2012-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.zookeeper.discovery.watcher.presence; + +/** + * @author instances = discoveryClient.getInstances(TEST_INSTANCE_NAME) + ServiceInstance instance = instances.first() + expect: + 'pong' == testRibbonClient.pingOnUrl("${instance.host}:${instance.port}") + } + + def 'should properly find local instance'() { + expect: + AddressProviderConfiguration.ipAddress == discoveryClient.localServiceInstance.host + } + + @Configuration + @EnableAutoConfiguration + @Import(CommonTestConfig) + @EnableDiscoveryClient + static class Config { + + @Bean + TestRibbonClient testRibbonClient(@LoadBalanced RestTemplate restTemplate, + @Value('${spring.application.name}') String springAppName) { + return new TestRibbonClient(restTemplate, springAppName) + } + + } + + static class TestRibbonClient extends TestServiceRestClient { + + private final String thisAppName + + TestRibbonClient(RestTemplate restTemplate, String thisAppName) { + super(restTemplate) + this.thisAppName = thisAppName + } + + String thisHealthCheck() { + return restTemplate.getForObject("http://$thisAppName/health", String) + } + + } +} diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryWithDependenciesISpec.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryWithDependenciesISpec.groovy new file mode 100644 index 00000000..0feab1c0 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryWithDependenciesISpec.groovy @@ -0,0 +1,83 @@ +/* + * Copyright 2012-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.zookeeper.discovery + +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.WireMock +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.SpringApplicationContextLoader +import org.springframework.cloud.client.ServiceInstance +import org.springframework.cloud.client.discovery.DiscoveryClient +import org.springframework.cloud.client.discovery.EnableDiscoveryClient +import org.springframework.cloud.client.loadbalancer.LoadBalanced +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Import +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration +import org.springframework.web.client.RestTemplate +import spock.lang.Specification + +import static com.github.tomakehurst.wiremock.client.WireMock.* + +@ContextConfiguration(classes = Config, loader = SpringApplicationContextLoader) +@ActiveProfiles('watcher') +class ZookeeperDiscoveryWithDependenciesISpec extends Specification { + + @Autowired TestRibbonClient testRibbonClient + @Autowired WireMockServer wiremockServer + @Autowired DiscoveryClient discoveryClient + WireMock wireMock + + def setup() { + wireMock = new WireMock('localhost', wiremockServer.port()) + wireMock.register(get(urlEqualTo('/ping')).willReturn(aResponse().withBody('pong'))) + } + + def 'should find a collaborator via Ribbon by using its alias from dependencies'() { + expect: + 'pong' == testRibbonClient.pingService('someAlias') + } + + def 'should find a collaborator via discovery client'() { + given: + List instances = discoveryClient.getInstances('someAlias') + ServiceInstance instance = instances.first() + expect: + 'pong' == testRibbonClient.pingOnUrl("${instance.host}:${instance.port}") + } + + @Configuration + @EnableAutoConfiguration + @Import(CommonTestConfig) + @EnableDiscoveryClient + static class Config { + + @Bean + TestRibbonClient testRibbonClient(@LoadBalanced RestTemplate restTemplate) { + return new TestRibbonClient(restTemplate) + } + + } + + static class TestRibbonClient extends TestServiceRestClient { + + TestRibbonClient(RestTemplate restTemplate) { + super(restTemplate) + } + } +} \ No newline at end of file diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/watcher/DefaultDependencyWatcherSpringISpec.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/watcher/DefaultDependencyWatcherSpringISpec.groovy new file mode 100644 index 00000000..3245501a --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/watcher/DefaultDependencyWatcherSpringISpec.groovy @@ -0,0 +1,133 @@ +/* + * Copyright 2012-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.zookeeper.discovery.watcher + +import org.apache.curator.framework.CuratorFramework +import org.apache.curator.framework.CuratorFrameworkFactory +import org.apache.curator.retry.ExponentialBackoffRetry +import org.apache.curator.test.TestingServer +import org.apache.curator.x.discovery.* +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.SpringApplicationContextLoader +import org.springframework.cloud.zookeeper.discovery.watcher.presence.DependencyPresenceOnStartupVerifier +import org.springframework.cloud.zookeeper.discovery.watcher.presence.LogMissingDependencyChecker +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration +import org.springframework.util.SocketUtils +import spock.lang.Specification +import spock.util.concurrent.PollingConditions + +@ContextConfiguration(classes = Config, loader = SpringApplicationContextLoader) +@ActiveProfiles('watcher') +class DefaultDependencyWatcherSpringISpec extends Specification { + + @Autowired AssertableDependencyPresenceOnStartupVerifier dependencyPresenceOnStartupVerifier + @Autowired AssertableDependencyWatcherListener dependencyWatcherListener + @Autowired ServiceDiscovery serviceDiscovery + @Autowired ServiceInstance serviceInstance + + def 'should verify that presence of a dependency has been checked'() { + expect: + dependencyPresenceOnStartupVerifier.startupPresenceVerified + } + + def 'should verify that dependency watcher listener is successfully registered and operational'() { + when: + serviceDiscovery.unregisterService(serviceInstance) + then: + new PollingConditions().eventually { + dependencyWatcherListener.dependencyState == DependencyState.DISCONNECTED + } + } + + @Configuration + @EnableAutoConfiguration + static class Config { + + @Bean + static PropertySourcesPlaceholderConfigurer propertiesConfigurer() { + return new PropertySourcesPlaceholderConfigurer() + } + + @Bean(destroyMethod = 'close') + TestingServer testingServer() { + return new TestingServer(SocketUtils.findAvailableTcpPort()) + } + + @Bean + ServiceInstance serviceInstance() { + return ServiceInstance.builder().uriSpec(new UriSpec("{scheme}://{address}:{port}/")) + .address('anyUrl') + .port(10) + .name('testInstance') + .build() + } + + @Bean(initMethod = 'start', destroyMethod = 'close') + ServiceDiscovery serviceDiscovery() { + return ServiceDiscoveryBuilder + .builder(Void) + .basePath('/') + .client(curatorFramework()) + .thisInstance(serviceInstance()) + .build() + } + + @Bean(initMethod = 'start', destroyMethod = 'close') + CuratorFramework curatorFramework() { + return CuratorFrameworkFactory.newClient(testingServer().connectString, new ExponentialBackoffRetry(20, 20, 500)) + } + + @Bean + DependencyWatcherListener dependencyWatcherListener() { + return new AssertableDependencyWatcherListener() + } + + @Bean + DependencyPresenceOnStartupVerifier dependencyPresenceOnStartupVerifier() { + return new AssertableDependencyPresenceOnStartupVerifier() + } + + } + + static class AssertableDependencyWatcherListener implements DependencyWatcherListener { + + DependencyState dependencyState = DependencyState.CONNECTED + + @Override + void stateChanged(String dependencyName, DependencyState newState) { + dependencyState = newState + } + } + + static class AssertableDependencyPresenceOnStartupVerifier extends DependencyPresenceOnStartupVerifier { + + boolean startupPresenceVerified = false + + AssertableDependencyPresenceOnStartupVerifier() { + super(new LogMissingDependencyChecker()) + } + + @Override + void verifyDependencyPresence(String dependencyName, ServiceCache serviceCache, boolean required) { + startupPresenceVerified = true + } + } +} diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/watcher/presence/DefaultDependencyPresenceOnStartupVerifierSpec.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/watcher/presence/DefaultDependencyPresenceOnStartupVerifierSpec.groovy new file mode 100644 index 00000000..95ae1253 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/watcher/presence/DefaultDependencyPresenceOnStartupVerifierSpec.groovy @@ -0,0 +1,38 @@ +/* + * Copyright 2012-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.zookeeper.discovery.watcher.presence + +import org.apache.curator.x.discovery.ServiceCache +import org.codehaus.groovy.runtime.StackTraceUtils +import spock.lang.Specification + +class DefaultDependencyPresenceOnStartupVerifierSpec extends Specification { + + private static final String SERVICE_NAME = 'service01' + + def 'should throw exception if obligatory dependencies are missing'() { + given: + DefaultDependencyPresenceOnStartupVerifier dependencyVerifier = new DefaultDependencyPresenceOnStartupVerifier() + ServiceCache serviceCache = Mock() + serviceCache.instances >> [] + when: + dependencyVerifier.verifyDependencyPresence(SERVICE_NAME, serviceCache, true) + then: + Throwable thrown = thrown(Throwable) + StackTraceUtils.extractRootCause(thrown).class == NoInstancesRunningException + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/watcher/presence/DependencyPresenceOnStartupVerifierSpec.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/watcher/presence/DependencyPresenceOnStartupVerifierSpec.groovy new file mode 100644 index 00000000..f8ed3809 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/watcher/presence/DependencyPresenceOnStartupVerifierSpec.groovy @@ -0,0 +1,38 @@ +/* + * Copyright 2012-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.zookeeper.discovery.watcher.presence + +import org.apache.curator.x.discovery.ServiceCache +import spock.lang.Specification + +class DependencyPresenceOnStartupVerifierSpec extends Specification { + + private static final String SERVICE_NAME = 'service01' + + def 'should check optional dependency using optional dependency checker'() { + given: + PresenceChecker optionalDependencyChecker = Mock() + DependencyPresenceOnStartupVerifier dependencyVerifier = new DependencyPresenceOnStartupVerifier(optionalDependencyChecker) { + } + ServiceCache serviceCache = Mock() + serviceCache.instances >> [] + when: + dependencyVerifier.verifyDependencyPresence(SERVICE_NAME, serviceCache, false) + then: + 1 * optionalDependencyChecker.checkPresence(SERVICE_NAME, serviceCache.instances) + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/test/resources/application-ribbon.yml b/spring-cloud-zookeeper-discovery/src/test/resources/application-ribbon.yml new file mode 100644 index 00000000..73852a66 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/resources/application-ribbon.yml @@ -0,0 +1 @@ +spring.application.name: ribbonApp \ No newline at end of file diff --git a/spring-cloud-zookeeper-discovery/src/test/resources/application-watcher.yml b/spring-cloud-zookeeper-discovery/src/test/resources/application-watcher.yml new file mode 100644 index 00000000..b28092a1 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/resources/application-watcher.yml @@ -0,0 +1,20 @@ +spring.application.name: someName +zookeeper: + dependencies: + someAlias: + id: someId + path: testInstance + loadBalancerType: ROUND_ROBIN + contentTypeTemplate: application/vnd.newsletter.$version+json + version: v1 + headers: + header1: value1 + header2: value2 + required: false + testInstance2: + id: someId2 + path: somePath2 + loadBalancerType: ROUND_ROBIN + contentTypeTemplate: application/vnd.newsletter.$version+json2 + version: v1 + required: false diff --git a/spring-cloud-zookeeper-sample/pom.xml b/spring-cloud-zookeeper-sample/pom.xml index 6f1c6658..40fb7488 100644 --- a/spring-cloud-zookeeper-sample/pom.xml +++ b/spring-cloud-zookeeper-sample/pom.xml @@ -1,58 +1,58 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - spring-cloud-zookeeper-sample - jar - Spring Cloud Zookeeper Sample - Spring Cloud Zookeeper Sample + spring-cloud-zookeeper-sample + jar + Spring Cloud Zookeeper Sample + Spring Cloud Zookeeper Sample - - org.springframework.cloud - spring-cloud-zookeeper - 1.0.0.BUILD-SNAPSHOT - .. - + + org.springframework.cloud + spring-cloud-zookeeper + 1.0.0.BUILD-SNAPSHOT + .. + - - - - - maven-deploy-plugin - - true - - - - + + + + + maven-deploy-plugin + + true + + + + - - - org.springframework.boot - spring-boot-starter-web - test - - - org.springframework.cloud - spring-cloud-zookeeper-config - test - - - org.springframework.cloud - spring-cloud-zookeeper-discovery - test - - - org.springframework.cloud - spring-cloud-zookeeper-bus - test - - - org.projectlombok - lombok - test - - + + + org.springframework.boot + spring-boot-starter-web + test + + + org.springframework.cloud + spring-cloud-zookeeper-config + test + + + org.springframework.cloud + spring-cloud-zookeeper-discovery + test + + + org.springframework.cloud + spring-cloud-zookeeper-bus + test + + + org.projectlombok + lombok + test + +