[#54] Created the list of statuses for nested dependencies. Fixes #54

This commit is contained in:
Marcin Grzejszczak
2015-12-15 14:13:16 +01:00
parent 6fe8ee48ac
commit 220cab870b
11 changed files with 308 additions and 27 deletions

View File

@@ -41,7 +41,7 @@ public class ZookeeperProperties {
/**
* @param maxRetries max number of times to retry
*/
private Integer maxRetries = 50;
private Integer maxRetries = 10;
/**
* @param maxSleepMs max time in ms to sleep on each retry

View File

@@ -75,13 +75,15 @@ public class ZookeeperDiscoveryClientConfiguration {
protected static class ZookeeperDiscoveryHealthConfig {
@Autowired
private ZookeeperServiceDiscovery serviceDiscovery;
@Autowired
private ZookeeperDiscoveryProperties zookeeperDiscoveryProperties;
@Autowired(required = false)
private ZookeeperDependencies zookeeperDependencies;
@Bean
@ConditionalOnMissingBean
public ZookeeperDiscoveryHealthIndicator zookeeperDiscoveryHealthIndicator() {
return new ZookeeperDiscoveryHealthIndicator(serviceDiscovery, zookeeperDependencies);
return new ZookeeperDiscoveryHealthIndicator(serviceDiscovery, zookeeperDependencies, zookeeperDiscoveryProperties);
}
}

View File

@@ -16,15 +16,15 @@
package org.springframework.cloud.zookeeper.discovery;
import java.util.ArrayList;
import java.util.Collection;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.x.discovery.ServiceInstance;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author Spencer Gibb
@@ -34,24 +34,20 @@ public class ZookeeperDiscoveryHealthIndicator extends AbstractHealthIndicator {
private ZookeeperServiceDiscovery serviceDiscovery;
private ZookeeperDependencies zookeeperDependencies;
private ZookeeperDiscoveryProperties zookeeperDiscoveryProperties;
public ZookeeperDiscoveryHealthIndicator(ZookeeperServiceDiscovery serviceDiscovery, ZookeeperDependencies zookeeperDependencies) {
public ZookeeperDiscoveryHealthIndicator(ZookeeperServiceDiscovery serviceDiscovery, ZookeeperDependencies zookeeperDependencies,
ZookeeperDiscoveryProperties zookeeperDiscoveryProperties) {
this.serviceDiscovery = serviceDiscovery;
this.zookeeperDependencies = zookeeperDependencies;
this.zookeeperDiscoveryProperties = zookeeperDiscoveryProperties;
}
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
try {
Collection<String> names = getNamesToQuery();
ArrayList<ServiceInstance<ZookeeperInstance>> allInstances = new ArrayList<>();
for (String name : names) {
Collection<ServiceInstance<ZookeeperInstance>> instances = this.serviceDiscovery
.getServiceDiscovery().queryForInstances(name);
for (ServiceInstance<ZookeeperInstance> instance : instances) {
allInstances.add(instance);
}
}
Iterable<ServiceInstance<ZookeeperInstance>> allInstances = new ZookeeperServiceInstances(serviceDiscovery,
zookeeperDependencies, zookeeperDiscoveryProperties);
builder.up().withDetail("services", allInstances);
}
catch (Exception e) {
@@ -59,11 +55,4 @@ public class ZookeeperDiscoveryHealthIndicator extends AbstractHealthIndicator {
builder.down(e);
}
}
private Collection<String> getNamesToQuery() throws Exception {
if (this.zookeeperDependencies == null) {
return this.serviceDiscovery.getServiceDiscovery().queryForNames();
}
return this.zookeeperDependencies.getDependencyNames();
}
}

View File

@@ -16,8 +16,6 @@
package org.springframework.cloud.zookeeper.discovery;
import javax.annotation.PostConstruct;
import com.netflix.client.config.IClientConfig;
import com.netflix.config.ConfigurationManager;
import com.netflix.config.DynamicPropertyFactory;
@@ -26,16 +24,20 @@ import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.PingUrl;
import com.netflix.loadbalancer.ServerList;
import lombok.extern.slf4j.Slf4j;
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.ConditionalOnDependenciesNotPassed;
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.Configuration;
import javax.annotation.PostConstruct;
import static com.netflix.client.config.CommonClientConfigKey.DeploymentContextBasedVipAddresses;
import static com.netflix.client.config.CommonClientConfigKey.EnableZoneAffinity;
@@ -49,6 +51,7 @@ import static com.netflix.client.config.CommonClientConfigKey.EnableZoneAffinity
* @author Marcin Grzejszczak, 4financeIT
*/
@Configuration
@Slf4j
public class ZookeeperRibbonClientConfiguration {
protected static final String VALUE_NOT_SET = "__not__set__";
protected static final String DEFAULT_NAMESPACE = "ribbon";
@@ -68,6 +71,7 @@ public class ZookeeperRibbonClientConfiguration {
public ServerList<?> ribbonServerListFromDependencies(IClientConfig config, ZookeeperDependencies zookeeperDependencies) {
ZookeeperServerList serverList = new ZookeeperServerList(this.serviceDiscovery.getServiceDiscovery());
serverList.initFromDependencies(config, zookeeperDependencies);
log.debug("Server list for Ribbon's dependencies based load balancing is [{}]", serverList);
return serverList;
}
@@ -89,9 +93,11 @@ public class ZookeeperRibbonClientConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnDependenciesNotPassed
public ServerList<?> ribbonServerList(IClientConfig config) {
ZookeeperServerList serverList = new ZookeeperServerList(this.serviceDiscovery.getServiceDiscovery());
serverList.initWithNiwsConfig(config);
log.debug("Server list for Ribbon's non-dependency based load balancing is [{}]", serverList);
return serverList;
}

View File

@@ -0,0 +1,118 @@
package org.springframework.cloud.zookeeper.discovery;
import lombok.extern.slf4j.Slf4j;
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.Iterator;
import java.util.List;
@Slf4j
public class ZookeeperServiceInstances implements Iterable<ServiceInstance<ZookeeperInstance>> {
private final ZookeeperServiceDiscovery serviceDiscovery;
private final ZookeeperDependencies zookeeperDependencies;
private final ZookeeperDiscoveryProperties zookeeperDiscoveryProperties;
private final List<ServiceInstance<ZookeeperInstance>> allInstances;
public ZookeeperServiceInstances(ZookeeperServiceDiscovery serviceDiscovery, ZookeeperDependencies zookeeperDependencies,
ZookeeperDiscoveryProperties zookeeperDiscoveryProperties) {
this.serviceDiscovery = serviceDiscovery;
this.zookeeperDependencies = zookeeperDependencies;
this.zookeeperDiscoveryProperties = zookeeperDiscoveryProperties;
this.allInstances = getZookeeperInstances();
}
private List<ServiceInstance<ZookeeperInstance>> getZookeeperInstances() {
ArrayList<ServiceInstance<ZookeeperInstance>> allInstances = new ArrayList<>();
try {
Collection<String> names = getNamesToQuery();
for (String name : names) {
allInstances.addAll(nestedInstances(allInstances, name));
}
return allInstances;
}
catch (Exception e) {
log.debug("Exception occurred while trying to build the list of instances", e);
return allInstances;
}
}
private List<ServiceInstance<ZookeeperInstance>> nestedInstances(
List<ServiceInstance<ZookeeperInstance>> accumulator, String name) throws Exception {
String parentPath = prepareQueryName(name);
Collection<ServiceInstance<ZookeeperInstance>> childrenInstances = tryToGetInstances(parentPath);
if (childrenInstances != null) {
return convertCollectionToList(childrenInstances);
}
try {
List<String> children = this.serviceDiscovery.getCurator().getChildren().forPath(parentPath);
return iterateOverChildren(accumulator, parentPath, children);
} catch (Exception e) {
log.trace("Exception occurred while trying to retrieve children of [" + parentPath + "]", e);
return injectZookeeperServiceInstances(accumulator, parentPath);
}
}
private String prepareQueryName(String name) {
if (name.startsWith(this.zookeeperDiscoveryProperties.getRoot())) {
return name;
}
String queryName = this.zookeeperDiscoveryProperties.getRoot();
if (!queryName.endsWith("/")) {
queryName = queryName + "/";
}
return queryName + name;
}
private Collection<ServiceInstance<ZookeeperInstance>> tryToGetInstances(String path) {
try {
return this.serviceDiscovery
.getServiceDiscovery().queryForInstances(path.substring(
this.zookeeperDiscoveryProperties.getRoot().length()));
} catch (Exception e) {
log.trace("Exception occurred while trying to retrieve instances of [" + path + "]", e);
return null;
}
}
private List<ServiceInstance<ZookeeperInstance>> injectZookeeperServiceInstances(
List<ServiceInstance<ZookeeperInstance>> accumulator, String name) throws Exception {
Collection<ServiceInstance<ZookeeperInstance>> instances = this.serviceDiscovery
.getServiceDiscovery().queryForInstances(name);
accumulator.addAll(convertCollectionToList(instances));
return accumulator;
}
private List<ServiceInstance<ZookeeperInstance>> convertCollectionToList(
Collection<ServiceInstance<ZookeeperInstance>> instances) {
List<ServiceInstance<ZookeeperInstance>> serviceInstances = new ArrayList<>();
for (ServiceInstance<ZookeeperInstance> instance : instances) {
serviceInstances.add(instance);
}
return serviceInstances;
}
private List<ServiceInstance<ZookeeperInstance>> iterateOverChildren(List<ServiceInstance<ZookeeperInstance>> accumulator,
String parentPath, List<String> children) throws Exception {
List<ServiceInstance<ZookeeperInstance>> lists = new ArrayList<>();
for (String child : children) {
lists.addAll(nestedInstances(accumulator, parentPath + "/" + child));
}
return lists;
}
private Collection<String> getNamesToQuery() throws Exception {
if (this.zookeeperDependencies == null) {
return this.serviceDiscovery.getServiceDiscovery().queryForNames();
}
return this.zookeeperDependencies.getDependencyNames();
}
@Override
public Iterator<ServiceInstance<ZookeeperInstance>> iterator() {
return allInstances.iterator();
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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 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 off a feature if Zookeeper dependencies have NOT been passed
*
* @author Marcin Grzejszczak
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(DependenciesNotPassedCondition.class)
public @interface ConditionalOnDependenciesNotPassed {
}

View File

@@ -55,6 +55,7 @@ public class DependenciesBasedLoadBalancer extends DynamicServerListLoadBalancer
public Server chooseServer(Object key) {
String keyAsString = (String) key;
ZookeeperDependency dependency = this.zookeeperDependencies.getDependencyForAlias(keyAsString);
log.debug("Current dependencies are [{}]", this.zookeeperDependencies);
if (dependency == null) {
log.debug("No dependency found for alias [{}] - will use the default rule which is [{}]", keyAsString, this.rule);
return this.rule.choose(key);

View File

@@ -0,0 +1,44 @@
/*
* 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 org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* Inverse of the {@link ConditionalOnDependenciesPassed} condition. Also checks if switch for zookeeper dependencies
* was turned on
*
* @author Marcin Grzejszczak
*/
public class DependenciesNotPassedCondition extends DependenciesPassedCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionOutcome propertiesSet = super.getMatchOutcome(context, metadata);
if (propertiesSet.isMatch()) {
return ConditionOutcome.inverse(propertiesSet);
}
Boolean dependenciesEnabled = context.getEnvironment()
.getProperty("spring.cloud.zookeeper.dependencies.enabled", Boolean.class, false);
if (dependenciesEnabled) {
return ConditionOutcome.noMatch("Dependencies are defined in configuration and switch is turned on");
}
return ConditionOutcome.match("Dependencies are not defined in configuration and switch is turned off");
}
}

View File

@@ -10,13 +10,19 @@ import org.apache.curator.x.discovery.UriSpec
class CustomZookeeperServiceDiscovery extends ZookeeperServiceDiscovery {
private final String applicationName
private final String basePath
CustomZookeeperServiceDiscovery(String applicationName, CuratorFramework curator) {
CustomZookeeperServiceDiscovery(String applicationName, String basePath, CuratorFramework curator) {
super(curator, null, null)
this.applicationName = applicationName
this.basePath = basePath
build()
}
CustomZookeeperServiceDiscovery(String applicationName, CuratorFramework curator) {
this(applicationName, '/', curator)
}
@Override
void build() {
setPort(10)
@@ -28,7 +34,7 @@ class CustomZookeeperServiceDiscovery extends ZookeeperServiceDiscovery {
getServiceInstanceRef().set(instance)
def discovery = ServiceDiscoveryBuilder
.builder(Void)
.basePath('/')
.basePath(basePath)
.client(getCurator())
.thisInstance(instance)
.build()

View File

@@ -0,0 +1,79 @@
package org.springframework.cloud.zookeeper.discovery
import groovy.json.JsonSlurper
import org.apache.curator.framework.CuratorFramework
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.test.SpringApplicationContextLoader
import org.springframework.boot.test.WebIntegrationTest
import org.springframework.cloud.client.discovery.EnableDiscoveryClient
import org.springframework.cloud.client.loadbalancer.LoadBalanced
import org.springframework.cloud.zookeeper.discovery.test.CommonTestConfig
import org.springframework.cloud.zookeeper.discovery.test.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.Issue
import spock.lang.Specification
import javax.annotation.PostConstruct
import javax.annotation.PreDestroy
@ContextConfiguration(classes = Config, loader = SpringApplicationContextLoader)
@ActiveProfiles('nestedstructure')
@WebIntegrationTest(randomPort = true)
class ZookeeperDiscoveryHealthIndicatorWithNestedStructureISpec extends Specification implements PollingUtils {
@Autowired TestRibbonClient testRibbonClient
@Issue("#54 - ZookeeperDiscoveryHealthIndicator fails on nested structure")
def 'should return a response that app is in a healthy state when nested folders in zookeeper are present'() {
when:
String response = testRibbonClient.callService('me', 'health')
then:
twoServicesArePresentedInHealthEndpoint(response)
}
private boolean twoServicesArePresentedInHealthEndpoint(String response) {
def services = new JsonSlurper().parseText(response).zookeeperDiscovery.services
assert ['me', '/a/b/c/d/anotherservice'].every { expectedServiceName ->
services.any { service ->
expectedServiceName == service.name
}
}
return true
}
@Configuration
@EnableAutoConfiguration
@EnableDiscoveryClient
@Import(CommonTestConfig)
@Profile('nestedstructure')
static class Config {
@Autowired CuratorFramework curatorFramework
CustomZookeeperServiceDiscovery customZookeeperServiceDiscovery
@PostConstruct
void registerNestedDependency() {
customZookeeperServiceDiscovery = new CustomZookeeperServiceDiscovery("/a/b/c/d/anotherservice",
'/services', curatorFramework).build()
}
@PreDestroy
void unregisterServiceDiscovery() {
customZookeeperServiceDiscovery?.close()
}
@Bean
TestRibbonClient testRibbonClient(@LoadBalanced RestTemplate restTemplate,
@Value('${spring.application.name}') String springAppName) {
return new TestRibbonClient(restTemplate, springAppName)
}
}
}

View File

@@ -0,0 +1 @@
spring.application.name: me