diff --git a/.travis.yml b/.travis.yml index 958d94a3..6e4067b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,9 @@ +sudo: false + +cache: + directories: + - $HOME/.m2 + language: java before_install: - git config user.name "$GIT_NAME" @@ -10,8 +16,8 @@ install: - mvn --settings .settings.xml install -P docs -q -U -DskipTests=true -Dmaven.test.redirectTestOutputToFile=true - ./docs/src/main/asciidoc/ghpages.sh script: -- '[ "${TRAVIS_PULL_REQUEST}" != "false" ] || mvn --settings .settings.xml deploy -nsu -Dmaven.test.redirectTestOutputToFile=true' -- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] || mvn --settings .settings.xml install -nsu -Dmaven.test.redirectTestOutputToFile=true' +- '[ "${TRAVIS_PULL_REQUEST}" != "false" ] || mvn --settings .settings.xml clean deploy -nsu' +- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] || mvn --settings .settings.xml clean install -nsu' env: global: - GIT_NAME="Spencer Gibb" diff --git a/pom.xml b/pom.xml index d233bd8f..0b81ab10 100644 --- a/pom.xml +++ b/pom.xml @@ -224,7 +224,7 @@ org.codehaus.groovy groovy-all - 2.4.3 + 2.4.4 test diff --git a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigBootstrapConfiguration.java b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/common/ZookeeperConfigBootstrapConfiguration.java similarity index 93% rename from spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigBootstrapConfiguration.java rename to spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/common/ZookeeperConfigBootstrapConfiguration.java index 87408dee..e0900993 100644 --- a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigBootstrapConfiguration.java +++ b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/common/ZookeeperConfigBootstrapConfiguration.java @@ -1,4 +1,4 @@ -package org.springframework.cloud.zookeeper.config; +package org.springframework.cloud.zookeeper.common; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.zookeeper.ZookeeperAutoConfiguration; diff --git a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigProperties.java b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/common/ZookeeperConfigProperties.java similarity index 90% rename from spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigProperties.java rename to spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/common/ZookeeperConfigProperties.java index 843aad81..a2782539 100644 --- a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperConfigProperties.java +++ b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/common/ZookeeperConfigProperties.java @@ -1,4 +1,4 @@ -package org.springframework.cloud.zookeeper.config; +package org.springframework.cloud.zookeeper.common; import lombok.Data; 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/common/ZookeeperPropertySource.java similarity index 98% rename from spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySource.java rename to spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/common/ZookeeperPropertySource.java index 0dfd46ce..b57bb233 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/common/ZookeeperPropertySource.java @@ -1,4 +1,4 @@ -package org.springframework.cloud.zookeeper.config; +package org.springframework.cloud.zookeeper.common; import java.nio.charset.Charset; import java.util.ArrayList; diff --git a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySourceLocator.java b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/common/ZookeeperPropertySourceLocator.java similarity index 97% rename from spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySourceLocator.java rename to spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/common/ZookeeperPropertySourceLocator.java index 5c4405b1..f90bf779 100644 --- a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySourceLocator.java +++ b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/common/ZookeeperPropertySourceLocator.java @@ -1,4 +1,4 @@ -package org.springframework.cloud.zookeeper.config; +package org.springframework.cloud.zookeeper.common; import java.util.ArrayList; import java.util.Arrays; diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ConditionalOnRibbonZookeeper.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ConditionalOnRibbonZookeeper.java new file mode 100644 index 00000000..2681fd2c --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/ConditionalOnRibbonZookeeper.java @@ -0,0 +1,17 @@ +package org.springframework.cloud.zookeeper.discovery; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Wrapper annotation to enable Ribbon for Zookeeper + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@ConditionalOnProperty(value = "ribbon.zookeeper.enabled", matchIfMissing = true) +public @interface ConditionalOnRibbonZookeeper { +} 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 0861194e..e0251f26 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 @@ -18,7 +18,6 @@ package org.springframework.cloud.zookeeper.discovery; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; import org.springframework.cloud.netflix.ribbon.RibbonClients; @@ -31,7 +30,7 @@ import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties @ConditionalOnBean(SpringClientFactory.class) -@ConditionalOnProperty(value = "ribbon.zookeeper.enabled", matchIfMissing = true) +@ConditionalOnRibbonZookeeper @AutoConfigureAfter(RibbonAutoConfiguration.class) @RibbonClients(defaultConfiguration = ZookeeperRibbonClientConfiguration.class) public class RibbonZookeeperAutoConfiguration { 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 e5f46889..6725f75d 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 @@ -20,15 +20,15 @@ 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.ILoadBalancer; import com.netflix.loadbalancer.ServerList; 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.ConditionalOnDependenciesPassed; +import org.springframework.cloud.zookeeper.discovery.dependency.DependenciesBasedLoadBalancer; 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; @@ -37,7 +37,7 @@ import static com.netflix.client.config.CommonClientConfigKey.DeploymentContextB import static com.netflix.client.config.CommonClientConfigKey.EnableZoneAffinity; /** - * Preprocessor that configures defaults for eureka-discovered ribbon clients. Such as: + * Preprocessor that configures defaults for zookeeper-discovered ribbon clients. Such as: * @zone, NIWSServerListClassName, DeploymentContextBasedVipAddresses, * NFLoadBalancerRuleClassName, NIWSServerListFilterClassName and more * @@ -61,14 +61,20 @@ public class ZookeeperRibbonClientConfiguration { @Bean @ConditionalOnMissingBean - @Conditional(DependenciesPassedCondition.class) - @ConditionalOnProperty(value = "spring.cloud.zookeeper.dependencies.enabled", matchIfMissing = true) + @ConditionalOnDependenciesPassed public ServerList ribbonServerListFromDependencies(IClientConfig config, ZookeeperDependencies zookeeperDependencies) { ZookeeperServerList serverList = new ZookeeperServerList(serviceDiscovery.getServiceDiscovery()); serverList.initFromDependencies(config, zookeeperDependencies); return serverList; } + @Bean + @ConditionalOnMissingBean + @ConditionalOnDependenciesPassed + public ILoadBalancer dependenciesBasedLoadBalancer(ZookeeperDependencies zookeeperDependencies, ServerList serverList) { + return new DependenciesBasedLoadBalancer(zookeeperDependencies, serverList); + } + @Bean @ConditionalOnMissingBean public ServerList ribbonServerList(IClientConfig config) { diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ConditionalOnDependenciesPassed.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ConditionalOnDependenciesPassed.java new file mode 100644 index 00000000..fb8980ba --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ConditionalOnDependenciesPassed.java @@ -0,0 +1,21 @@ +package org.springframework.cloud.zookeeper.discovery.dependency; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to turn on a feature if Zookeeper dependencies have been passed + * + * @author Marcin Grzejszczak, 4financeIT + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Conditional(DependenciesPassedCondition.class) +@ConditionalOnProperty(value = "spring.cloud.zookeeper.dependencies.enabled", matchIfMissing = true) +public @interface ConditionalOnDependenciesPassed { +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependenciesBasedLoadBalancer.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependenciesBasedLoadBalancer.java new file mode 100644 index 00000000..8b8dedf0 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependenciesBasedLoadBalancer.java @@ -0,0 +1,75 @@ +package org.springframework.cloud.zookeeper.discovery.dependency; + +import com.netflix.loadbalancer.*; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * LoadBalancer that delegates to other rules depending on the provided load balancing strategy + * in the {@link ZookeeperDependencies.ZookeeperDependency#getLoadBalancerType()} + * + * @author Marcin Grzejszczak, 4financeIT + */ +@Slf4j +public class DependenciesBasedLoadBalancer extends BaseLoadBalancer { + + private final Map ruleCache = new ConcurrentHashMap<>(); + + private final ZookeeperDependencies zookeeperDependencies; + + public DependenciesBasedLoadBalancer(ZookeeperDependencies zookeeperDependencies, ServerList serverList) { + this.zookeeperDependencies = zookeeperDependencies; + setServersList(serverList.getInitialListOfServers()); + } + + @Override + public Server chooseServer(Object key) { + String keyAsString = (String) key; + ZookeeperDependencies.ZookeeperDependency dependency = zookeeperDependencies.getDependencyForAlias(keyAsString); + if (dependency == null) { + log.debug("No dependency found for alias [{}] - will use the default rule which is [{}]", keyAsString, rule); + return rule.choose(key); + }; + cacheEntryIfMissing(keyAsString, dependency); + log.debug("Will try to retrieve dependency for key [{}]. Current cache contents [{}]", keyAsString, ruleCache); + return ruleCache.get(keyAsString).choose(key); + } + + private void cacheEntryIfMissing(String keyAsString, ZookeeperDependencies.ZookeeperDependency dependency) { + if (!ruleCache.containsKey(keyAsString)) { + log.debug("Cache doesn't contain entry for [{}]", keyAsString); + ruleCache.put(keyAsString, chooseRuleForLoadBalancerType(dependency.getLoadBalancerType())); + } + } + + private IRule chooseRuleForLoadBalancerType(LoadBalancerType type) { + switch (type) { + case ROUND_ROBIN: + return getRoundRobinRule(); + case RANDOM: + return getRandomRule(); + case STICKY: + return getStickyRule(); + default: + throw new IllegalArgumentException("Unknown load balancer type " + type); + } + } + + private RoundRobinRule getRoundRobinRule() { + return new RoundRobinRule(this); + } + + private IRule getRandomRule() { + RandomRule randomRule = new RandomRule(); + randomRule.setLoadBalancer(this); + return randomRule; + } + + private IRule getStickyRule() { + StickyRule stickyRule = new StickyRule(getRoundRobinRule()); + stickyRule.setLoadBalancer(this); + return stickyRule; + } +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependencyRibbonAutoConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependencyRibbonAutoConfiguration.java new file mode 100644 index 00000000..a808d0a1 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependencyRibbonAutoConfiguration.java @@ -0,0 +1,65 @@ +/* + * 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.zookeeper.discovery.dependency; + +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; +import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.springframework.cloud.zookeeper.discovery.ConditionalOnRibbonZookeeper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * + * Provides LoadBalancerClient that at runtime can pick proper load balancing strategy + * basing on the Zookeeper dependencies from properties + * + * @author Marcin Grzejszczak, 4financeIT + */ +@AutoConfigureBefore(RibbonAutoConfiguration.class) +@ConditionalOnRibbonZookeeper +@Configuration +@Slf4j +public class DependencyRibbonAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnDependenciesPassed + public LoadBalancerClient loadBalancerClient(SpringClientFactory springClientFactory) { + return new RibbonLoadBalancerClient(springClientFactory) { + @Override + protected Server getServer(String serviceId) { + ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); + return loadBalancer == null ? null : chooseServerByServiceIdOrDefault(loadBalancer, serviceId); + } + + private Server chooseServerByServiceIdOrDefault(ILoadBalancer loadBalancer, String serviceId) { + log.debug("Dependencies are set - will try to load balance via provided load balancer [{}] for key [{}]", loadBalancer, serviceId); + Server server = loadBalancer.chooseServer(serviceId); + log.debug("Retrieved server [{}] via load balancer", server); + return server != null ? server : loadBalancer.chooseServer("default"); + } + }; + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/StickyRule.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/StickyRule.java new file mode 100644 index 00000000..6da517d2 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/StickyRule.java @@ -0,0 +1,64 @@ +package org.springframework.cloud.zookeeper.discovery.dependency; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.AbstractLoadBalancerRule; +import com.netflix.loadbalancer.IRule; +import com.netflix.loadbalancer.Server; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Load balancing rule that returns always the same instance. + * + * Ported from {@link org.apache.curator.x.discovery.strategies.StickyStrategy} + * + * author: Marcin Grzejszczak, 4financeIT + */ +@Slf4j +public class StickyRule extends AbstractLoadBalancerRule { + private final IRule masterStrategy; + private final AtomicReference ourInstance = new AtomicReference<>(null); + private final AtomicInteger instanceNumber = new AtomicInteger(-1); + + public StickyRule(IRule masterStrategy) { + this.masterStrategy = masterStrategy; + } + + @Override + public void initWithNiwsConfig(IClientConfig iClientConfig) { + + } + + @Override + public Server choose(Object key) { + final List instances = getLoadBalancer().getServerList(true); + log.debug("Instances taken from load balancer {}", instances); + Server localOurInstance = ourInstance.get(); + log.debug("Current saved instance [{}]", localOurInstance); + if (!instances.contains(localOurInstance)) { + ourInstance.compareAndSet(localOurInstance, null); + } + if (ourInstance.get() == null) { + Server instance = masterStrategy.choose(key); + if (ourInstance.compareAndSet(null, instance)) { + instanceNumber.incrementAndGet(); + } + } + return ourInstance.get(); + } + + /** + * Each time a new instance is picked, an internal counter is incremented. This way you + * can track when/if the instance changes. The instance can change when the selected instance + * is not in the current list of instances returned by the instance provider + * + * @return instance number + */ + public int getInstanceNumber() { + return instanceNumber.get(); + } + +} 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 index 516bd8a4..aabecc21 100644 --- 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 @@ -56,7 +56,7 @@ public class ZookeeperDependencies { private String path; - private LoadBalancerType loadBalancerType; + private LoadBalancerType loadBalancerType = LoadBalancerType.ROUND_ROBIN; private String contentTypeTemplate; @@ -65,6 +65,7 @@ public class ZookeeperDependencies { private Map headers; private boolean required; + } public Collection getDependencyConfigurations() { @@ -75,6 +76,15 @@ public class ZookeeperDependencies { return !dependencies.isEmpty(); } + public ZookeeperDependency getDependencyForPath(final String path) { + for (Map.Entry zookeeperDependencyEntry : dependencies.entrySet()) { + if (zookeeperDependencyEntry.getValue().getPath().equals(path)) { + return zookeeperDependencyEntry.getValue(); + } + } + return null; + } + public ZookeeperDependency getDependencyForAlias(final String alias) { for (Map.Entry zookeeperDependencyEntry : dependencies.entrySet()) { if (zookeeperDependencyEntry.getKey().equals(alias)) { 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 index fedb4e40..73ad22e4 100644 --- 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 @@ -17,11 +17,9 @@ 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; /** @@ -34,8 +32,7 @@ import org.springframework.context.annotation.Configuration; */ @Configuration @EnableConfigurationProperties -@Conditional(DependenciesPassedCondition.class) -@ConditionalOnProperty(value = "spring.cloud.zookeeper.dependencies.enabled", matchIfMissing = true) +@ConditionalOnDependenciesPassed @AutoConfigureAfter(ZookeeperAutoConfiguration.class) public class ZookeeperDependenciesAutoConfiguration { 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 index b10613ac..169751eb 100644 --- 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 @@ -15,24 +15,22 @@ */ package org.springframework.cloud.zookeeper.discovery.watcher; -import java.util.ArrayList; -import java.util.List; - 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.ZookeeperServiceDiscovery; -import org.springframework.cloud.zookeeper.discovery.dependency.DependenciesPassedCondition; +import org.springframework.cloud.zookeeper.discovery.dependency.ConditionalOnDependenciesPassed; 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. @@ -43,8 +41,7 @@ import org.springframework.context.annotation.Configuration; */ @Configuration @EnableConfigurationProperties -@Conditional(DependenciesPassedCondition.class) -@ConditionalOnProperty(value = "spring.cloud.zookeeper.dependencies.enabled", matchIfMissing = true) +@ConditionalOnDependenciesPassed @AutoConfigureAfter(ZookeeperDependenciesAutoConfiguration.class) public class DependencyWatcherAutoConfiguration { diff --git a/spring-cloud-zookeeper-discovery/src/main/resources/META-INF/spring.factories b/spring-cloud-zookeeper-discovery/src/main/resources/META-INF/spring.factories index 93d38d8e..6e8246ff 100644 --- a/spring-cloud-zookeeper-discovery/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-zookeeper-discovery/src/main/resources/META-INF/spring.factories @@ -1,5 +1,6 @@ # Auto Configuration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.zookeeper.discovery.dependency.DependencyRibbonAutoConfiguration,\ org.springframework.cloud.zookeeper.discovery.RibbonZookeeperAutoConfiguration,\ org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependenciesAutoConfiguration,\ org.springframework.cloud.zookeeper.discovery.watcher.DependencyWatcherAutoConfiguration diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/CommonTestConfig.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/common/CommonTestConfig.groovy similarity index 70% rename from spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/CommonTestConfig.groovy rename to spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/common/CommonTestConfig.groovy index 46271055..c4053bb6 100644 --- a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/CommonTestConfig.groovy +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/common/CommonTestConfig.groovy @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.cloud.zookeeper.discovery +package org.springframework.cloud.zookeeper.common -import com.github.tomakehurst.wiremock.WireMockServer import groovy.transform.CompileStatic import groovy.transform.PackageScope -import org.apache.curator.framework.CuratorFramework import org.apache.curator.test.TestingServer import org.springframework.cloud.zookeeper.ZookeeperProperties import org.springframework.context.annotation.Bean @@ -34,15 +32,6 @@ class CommonTestConfig { return new TestingServer(SocketUtils.findAvailableTcpPort()) } - @Bean(initMethod = "start", destroyMethod = "stop") - TestServiceRegistrar testServiceRegistrar(CuratorFramework curatorFramework) { - return new TestServiceRegistrar(wiremockServer().port(), curatorFramework) - } - - @Bean(initMethod = "start", destroyMethod = "shutdown") WireMockServer wiremockServer() { - return new WireMockServer(SocketUtils.findAvailableTcpPort()) - } - @Bean ZookeeperProperties zookeeperProperties() { return new ZookeeperProperties(connectString: "localhost:${testingServer().port}") } diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/common/TestRibbonClient.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/common/TestRibbonClient.groovy new file mode 100644 index 00000000..c546f8cd --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/common/TestRibbonClient.groovy @@ -0,0 +1,23 @@ +package org.springframework.cloud.zookeeper.common + +import org.springframework.web.client.RestTemplate + +class TestRibbonClient extends TestServiceRestClient { + + private final String thisAppName + + TestRibbonClient(RestTemplate restTemplate) { + super(restTemplate) + this.thisAppName = 'someName' + } + + TestRibbonClient(RestTemplate restTemplate, String thisAppName) { + super(restTemplate) + this.thisAppName = thisAppName + } + + String thisHealthCheck() { + return restTemplate.getForObject("http://$thisAppName/health", String) + } + +} \ No newline at end of file diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/TestServiceRestClient.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/common/TestServiceRestClient.groovy similarity index 74% rename from spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/TestServiceRestClient.groovy rename to spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/common/TestServiceRestClient.groovy index 75060d89..7ca681ec 100644 --- a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/TestServiceRestClient.groovy +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/common/TestServiceRestClient.groovy @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.cloud.zookeeper.discovery +package org.springframework.cloud.zookeeper.common -import groovy.transform.PackageScope import groovy.transform.CompileStatic import org.springframework.web.client.RestTemplate -@PackageScope @CompileStatic class TestServiceRestClient { @@ -29,11 +27,11 @@ class TestServiceRestClient { this.restTemplate = restTemplate } - String pingService(String alias) { - return restTemplate.getForObject("http://$alias/ping", String) + String callService(String alias, String endpoint) { + return restTemplate.getForObject("http://$alias/$endpoint", String) } - String pingOnUrl(String url) { - return new RestTemplate().getForObject("http://$url/ping", String) + String callOnUrl(String url, String endpoint) { + return new RestTemplate().getForObject("http://$url/$endpoint", String) } } diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/PollingUtils.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/PollingUtils.groovy new file mode 100644 index 00000000..251c14ea --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/PollingUtils.groovy @@ -0,0 +1,14 @@ +package org.springframework.cloud.zookeeper.discovery + +trait PollingUtils { + Closure willPass(Closure closure) { + return { + try { + closure() + } catch (Exception e) { + throw new AssertionError("Exception occurred while evaluating closure", e) + } + } + } + +} diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/TestServiceRegistrar.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/TestServiceRegistrar.groovy index ff8891d7..b6f9cec2 100644 --- a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/TestServiceRegistrar.groovy +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/TestServiceRegistrar.groovy @@ -25,12 +25,12 @@ import org.apache.curator.x.discovery.UriSpec @CompileStatic class TestServiceRegistrar { - private final int wiremockServerPort + private final int serverPort private final CuratorFramework curatorFramework private final ServiceDiscovery serviceDiscovery - TestServiceRegistrar(int wiremockServerPort, CuratorFramework curatorFramework) { - this.wiremockServerPort = wiremockServerPort + TestServiceRegistrar(int serverPort, CuratorFramework curatorFramework) { + this.serverPort = serverPort this.curatorFramework = curatorFramework this.serviceDiscovery = serviceDiscovery() } @@ -42,7 +42,7 @@ class TestServiceRegistrar { ServiceInstance serviceInstance() { return ServiceInstance.builder().uriSpec(new UriSpec("{scheme}://{address}:{port}/")) .address('localhost') - .port(wiremockServerPort) + .port(serverPort) .name('testInstance') .build() } diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryISpec.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryISpec.groovy index f62bb21d..e8b52a83 100644 --- a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryISpec.groovy +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryISpec.groovy @@ -15,8 +15,6 @@ */ package org.springframework.cloud.zookeeper.discovery -import com.github.tomakehurst.wiremock.WireMockServer -import com.github.tomakehurst.wiremock.client.WireMock import groovy.json.JsonSlurper import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value @@ -27,53 +25,47 @@ 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.cloud.zookeeper.common.CommonTestConfig +import org.springframework.cloud.zookeeper.common.TestRibbonClient import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import +import org.springframework.context.annotation.Profile 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('ribbon') @WebIntegrationTest(randomPort = true) class ZookeeperDiscoveryISpec extends Specification { - public static final String TEST_INSTANCE_NAME = 'testInstance' - @Autowired TestRibbonClient testRibbonClient - @Autowired WireMockServer wiremockServer @Autowired DiscoveryClient discoveryClient @Autowired ZookeeperServiceDiscovery serviceDiscovery - WireMock wireMock + @Value('${spring.application.name}') String springAppName - def setup() { - wireMock = new WireMock('localhost', wiremockServer.port()) - wireMock.register(get(urlEqualTo('/ping')).willReturn(aResponse().withBody('pong'))) - } - - def 'should find a collaborator via Ribbon'() { - expect: - 'pong' == testRibbonClient.pingService(TEST_INSTANCE_NAME) - } def 'should find the app by its name via Ribbon'() { - given: - def jsonSlurper = new JsonSlurper() - def health = jsonSlurper.parseText(testRibbonClient.thisHealthCheck()) expect: - 'UP' == health.status + 'UP' == registeredServiceStatusViaServiceName() } def 'should find a collaborator via discovery client'() { given: - List instances = discoveryClient.getInstances(TEST_INSTANCE_NAME) + List instances = discoveryClient.getInstances(springAppName) ServiceInstance instance = instances.first() expect: - 'pong' == testRibbonClient.pingOnUrl("${instance.host}:${instance.port}") + 'UP' == registeredServiceStatus(instance) + } + + private String registeredServiceStatusViaServiceName() { + return new JsonSlurper().parseText(testRibbonClient.thisHealthCheck()).status + } + + private String registeredServiceStatus(ServiceInstance instance) { + return new JsonSlurper().parseText(testRibbonClient.callOnUrl("${instance.host}:${instance.port}", 'health')).status } def 'should properly find local instance'() { @@ -85,6 +77,7 @@ class ZookeeperDiscoveryISpec extends Specification { @EnableAutoConfiguration @Import(CommonTestConfig) @EnableDiscoveryClient + @Profile('ribbon') static class Config { @Bean @@ -92,21 +85,5 @@ class ZookeeperDiscoveryISpec extends Specification { @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/dependency/StickyRuleISpec.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/StickyRuleISpec.groovy new file mode 100644 index 00000000..0bb581d7 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/StickyRuleISpec.groovy @@ -0,0 +1,95 @@ +/* + * 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.curator.framework.CuratorFramework +import org.apache.curator.test.TestingServer +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.SpringApplicationContextLoader +import org.springframework.boot.test.WebIntegrationTest +import org.springframework.cloud.client.discovery.DiscoveryClient +import org.springframework.cloud.client.discovery.EnableDiscoveryClient +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient +import org.springframework.cloud.zookeeper.ZookeeperProperties +import org.springframework.cloud.zookeeper.discovery.PollingUtils +import org.springframework.cloud.zookeeper.discovery.TestServiceRegistrar +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Profile +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('loadbalancerclient') +@WebIntegrationTest(randomPort = true) +class StickyRuleISpec extends Specification implements PollingUtils { + + @Autowired LoadBalancerClient loadBalancerClient + @Autowired DiscoveryClient discoveryClient + PollingConditions conditions + + def setup() { + conditions = new PollingConditions() + } + + def 'should use sticky load balancing strategy taken from Zookeeper dependencies'() { + expect: + thereAreTwoRegisteredServices() + URI uri = getUriForAlias() + conditions.eventually willPass { + 2.times { + assert uri == getUriForAlias() + } + } + } + + private boolean thereAreTwoRegisteredServices() { + return discoveryClient.getInstances('someAlias')?.size() == 2 + } + + private URI getUriForAlias() { + return loadBalancerClient.choose('someAlias')?.uri + } + + + @Configuration + @EnableAutoConfiguration + @EnableDiscoveryClient + @Profile('loadbalancerclient') + static class Config { + + @Bean(destroyMethod = 'close') + TestingServer testingServer() { + return new TestingServer(SocketUtils.findAvailableTcpPort()) + } + + @Bean ZookeeperProperties zookeeperProperties() { + return new ZookeeperProperties(connectString: "localhost:${testingServer().port}") + } + + @Bean(initMethod = "start", destroyMethod = "stop") TestServiceRegistrar serviceOne(CuratorFramework curatorFramework) { + return new TestServiceRegistrar(SocketUtils.findAvailableTcpPort(), curatorFramework) + } + + @Bean(initMethod = "start", destroyMethod = "stop") TestServiceRegistrar serviceTwo(CuratorFramework curatorFramework) { + return new TestServiceRegistrar(SocketUtils.findAvailableTcpPort(), curatorFramework) + } + + } +} \ No newline at end of file diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependenciesSpec.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependenciesSpec.groovy new file mode 100644 index 00000000..927e1ebb --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependenciesSpec.groovy @@ -0,0 +1,64 @@ +package org.springframework.cloud.zookeeper.discovery.dependency + +import spock.lang.Specification +import spock.lang.Unroll + +class ZookeeperDependenciesSpec extends Specification { + + private static final ZookeeperDependencies.ZookeeperDependency EXPECTED_DEPENDENCY = new ZookeeperDependencies.ZookeeperDependency( + 'id', + 'path', + LoadBalancerType.RANDOM, + 'contentTypeTemplate', + 'version', + [header: 'value'], + false + ) + private static final Map DEPENDENCIES = [ + alias: EXPECTED_DEPENDENCY + ] + + ZookeeperDependencies zookeeperDependencies = new ZookeeperDependencies(dependencies: DEPENDENCIES) + + @Unroll + def "should retrieve dependency [#expectedDependency] for path [#path]"() { + expect: + expectedDependency == zookeeperDependencies.getDependencyForPath(path) + where: + path || expectedDependency + 'unknownPath' || null + 'path' || EXPECTED_DEPENDENCY + } + + @Unroll + def "should retrieve dependency [#expectedDependency] for alias [#alias]"() { + expect: + expectedDependency == zookeeperDependencies.getDependencyForAlias(alias) + where: + alias || expectedDependency + 'unknownAlias' || null + 'alias' || EXPECTED_DEPENDENCY + } + + @Unroll + def "should retrieve alias [#expectedAlias] for path [#path]"() { + expect: + expectedAlias == zookeeperDependencies.getAliasForPath(path) + where: + path || expectedAlias + 'unknownPath' || '' + 'path' || 'alias' + } + + @Unroll + def "should retrieve path [#expectedPath] for alias [#alias]"() { + expect: + expectedPath == zookeeperDependencies.getPathForAlias(alias) + where: + alias || expectedPath + 'unknownAlias' || '' + 'alias' || 'path' + } + + +} 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/dependency/ZookeeperDiscoveryWithDependenciesISpec.groovy similarity index 63% rename from spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/ZookeeperDiscoveryWithDependenciesISpec.groovy rename to spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDiscoveryWithDependenciesISpec.groovy index cef1479f..33ab8ca5 100644 --- 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/dependency/ZookeeperDiscoveryWithDependenciesISpec.groovy @@ -13,10 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.cloud.zookeeper.discovery +package org.springframework.cloud.zookeeper.discovery.dependency -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 @@ -25,39 +23,46 @@ 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.cloud.zookeeper.common.CommonTestConfig +import org.springframework.cloud.zookeeper.common.TestRibbonClient +import org.springframework.cloud.zookeeper.discovery.PollingUtils import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import +import org.springframework.context.annotation.Profile +import org.springframework.stereotype.Controller import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration +import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.client.RestTemplate import spock.lang.Specification - -import static com.github.tomakehurst.wiremock.client.WireMock.* +import spock.util.concurrent.PollingConditions @ContextConfiguration(classes = Config, loader = SpringApplicationContextLoader) -@ActiveProfiles('watcher') +@ActiveProfiles('dependencies') @WebIntegrationTest(randomPort = true) -class ZookeeperDiscoveryWithDependenciesISpec extends Specification { +class ZookeeperDiscoveryWithDependenciesISpec extends Specification implements PollingUtils { @Autowired TestRibbonClient testRibbonClient - @Autowired WireMockServer wiremockServer @Autowired DiscoveryClient discoveryClient - WireMock wireMock + PollingConditions conditions def setup() { - wireMock = new WireMock('localhost', wiremockServer.port()) - wireMock.register(get(urlEqualTo('/ping')).willReturn(aResponse().withBody('pong'))) + conditions = new PollingConditions() } def 'should find an instance via path when alias is not found'() { expect: - !discoveryClient.getInstances('some/name/without/alias').empty + conditions.eventually willPass { + assert !discoveryClient.getInstances('nameWithoutAlias').empty + } } def 'should find a collaborator via Ribbon by using its alias from dependencies'() { expect: - 'pong' == testRibbonClient.pingService('someAlias') + conditions.eventually willPass { + assert callingServiceAtBeansEndpointIsNotEmpty() + } } def 'should find a collaborator via discovery client'() { @@ -65,13 +70,24 @@ class ZookeeperDiscoveryWithDependenciesISpec extends Specification { List instances = discoveryClient.getInstances('someAlias') ServiceInstance instance = instances.first() expect: - 'pong' == testRibbonClient.pingOnUrl("${instance.host}:${instance.port}") + conditions.eventually willPass { + assert callingServiceViaUrlOnBeansEndpointIsNotEmpty(instance) + } + } + + private boolean callingServiceAtBeansEndpointIsNotEmpty() { + return !testRibbonClient.callService('someAlias', 'beans').empty + } + + private boolean callingServiceViaUrlOnBeansEndpointIsNotEmpty(ServiceInstance instance) { + return !testRibbonClient.callOnUrl("${instance.host}:${instance.port}", 'beans').empty } @Configuration @EnableAutoConfiguration @Import(CommonTestConfig) @EnableDiscoveryClient + @Profile('dependencies') static class Config { @Bean @@ -81,10 +97,12 @@ class ZookeeperDiscoveryWithDependenciesISpec extends Specification { } - static class TestRibbonClient extends TestServiceRestClient { + @Controller + @Profile('dependencies') + class PingController { - TestRibbonClient(RestTemplate restTemplate) { - super(restTemplate) + @RequestMapping('/ping') String ping() { + return 'pong' } } } \ 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 index 7f604fc4..6640231b 100644 --- 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 @@ -14,7 +14,6 @@ * 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 @@ -26,11 +25,13 @@ import org.apache.curator.x.discovery.UriSpec 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.PollingUtils import org.springframework.cloud.zookeeper.discovery.ZookeeperServiceDiscovery 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.annotation.Profile import org.springframework.context.support.PropertySourcesPlaceholderConfigurer import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration @@ -42,12 +43,16 @@ import javax.annotation.PreDestroy @ContextConfiguration(classes = Config, loader = SpringApplicationContextLoader) @ActiveProfiles('watcher') -class DefaultDependencyWatcherSpringISpec extends Specification { +class DefaultDependencyWatcherSpringISpec extends Specification implements PollingUtils { @Autowired AssertableDependencyPresenceOnStartupVerifier dependencyPresenceOnStartupVerifier @Autowired AssertableDependencyWatcherListener dependencyWatcherListener - @Autowired - ZookeeperServiceDiscovery serviceDiscovery + @Autowired ZookeeperServiceDiscovery serviceDiscovery + PollingConditions conditions + + def setup() { + conditions = new PollingConditions() + } def 'should verify that presence of a dependency has been checked'() { expect: @@ -56,15 +61,17 @@ class DefaultDependencyWatcherSpringISpec extends Specification { def 'should verify that dependency watcher listener is successfully registered and operational'() { when: - serviceDiscovery.serviceDiscovery.unregisterService(serviceDiscovery.serviceInstance) + serviceDiscovery.serviceDiscovery.unregisterService(serviceDiscovery.serviceInstance) + then: - new PollingConditions().eventually { - dependencyWatcherListener.dependencyState == DependencyState.DISCONNECTED + conditions.eventually willPass { + assert dependencyWatcherListener.dependencyState == DependencyState.DISCONNECTED } } @Configuration @EnableAutoConfiguration + @Profile('watcher') static class Config { @Bean @@ -79,9 +86,7 @@ class DefaultDependencyWatcherSpringISpec extends Specification { @Bean ZookeeperServiceDiscovery zookeeperServiceDiscovery() { - return new MyZookeeperServiceDiscovery(curatorFramework()) { - - } + return new MyZookeeperServiceDiscovery(curatorFramework()) } @Bean(initMethod = 'start', destroyMethod = 'close') diff --git a/spring-cloud-zookeeper-discovery/src/test/resources/application-dependencies.yml b/spring-cloud-zookeeper-discovery/src/test/resources/application-dependencies.yml new file mode 100644 index 00000000..502c8393 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/resources/application-dependencies.yml @@ -0,0 +1,20 @@ +spring.application.name: nameWithoutAlias +spring.cloud.zookeeper: + dependencies: + someAlias: + id: someId + path: nameWithoutAlias + 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-discovery/src/test/resources/application-loadbalancerclient.yml b/spring-cloud-zookeeper-discovery/src/test/resources/application-loadbalancerclient.yml new file mode 100644 index 00000000..50ae0842 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/resources/application-loadbalancerclient.yml @@ -0,0 +1,7 @@ +spring.application.name: loadbalancerclient +spring.cloud.zookeeper: + dependencies: + someAlias: + id: someId + path: testInstance + loadBalancerType: STICKY diff --git a/spring-cloud-zookeeper-discovery/src/test/resources/logback-test.xml b/spring-cloud-zookeeper-discovery/src/test/resources/logback-test.xml new file mode 100644 index 00000000..f93b3936 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/resources/logback-test.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file