initial commit

This commit is contained in:
Spencer Gibb
2015-01-28 14:35:15 -07:00
commit 87357d2732
35 changed files with 1559 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
*~
#*
*#
.#*
.classpath
.project
.settings/
.springBeans
target/
_site/
.idea
*.iml
*.swp

15
README.md Normal file
View File

@@ -0,0 +1,15 @@
spring-cloud-zookeeper
===================
Developer Preview
### TODO
- [X] zookeeper config
- [X] zookeeper service discovery
- [X] zookeeper ribbon load balancer
- [ ] zookeeper ui
- [X] zookeeper property source
- [ ] zookeeper event bus
- [ ] send messages
- [ ] receive messages

177
pom.xml Normal file
View File

@@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Cloud Zookeeper</name>
<description>Spring Cloud Zookeeper</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<scm>
<url>https://github.com/spring-cloud/spring-cloud-zookeeper</url>
<connection>
scm:git:git://github.com/spring-cloud/spring-cloud-zookeeper.git
</connection>
<developerConnection>
scm:git:ssh://git@github.com/spring-cloud/spring-cloud-zookeeper.git
</developerConnection>
<tag>HEAD</tag>
</scm>
<modules>
<module>spring-cloud-zookeeper-core</module>
<module>spring-cloud-zookeeper-config</module>
<module>spring-cloud-zookeeper-discovery</module>
<module>spring-cloud-zookeeper-bus</module>
<module>spring-cloud-zookeeper-sample</module>
</modules>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-core</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-bus</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-config</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-discovery</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-amqp</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-spring-service-connector
</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-localconfig-connector
</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-cloudfoundry-connector
</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${curator.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>${curator.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-test</artifactId>
<version>${curator.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon</artifactId>
<version>${ribbon.version}</version>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-core</artifactId>
<version>${ribbon.version}</version>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-httpclient</artifactId>
<version>${ribbon.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.12.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<curator.version>2.7.0</curator.version>
<ribbon.version>2.0-RC9</ribbon.version>
</properties>
</project>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-zookeeper-bus</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Zookeeper Bus</name>
<description>Spring Cloud Zookeeper Bus</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- Only needed at compile time -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,27 @@
package org.springframework.cloud.zookeeper.bus;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
/**
* @author Spencer Gibb
*/
@JsonTypeName("simple")
@Data
public class SimpleRemoteEvent extends RemoteApplicationEvent {
private String message;
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 message) {
super(source, originService);
this.message = message;
}
}

View File

@@ -0,0 +1,3 @@
# Auto Configuration
#org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
#org.springframework.cloud.zookeeper.bus.ZookeeperBusAutoConfiguration

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-zookeeper-config</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Zookeeper Config</name>
<description>Spring Cloud Zookeeper Config</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- Only needed at compile time -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,25 @@
package org.springframework.cloud.zookeeper.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.zookeeper.ZookeeperAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author Spencer Gibb
*/
@Configuration
@Import(ZookeeperAutoConfiguration.class)
@EnableConfigurationProperties
public class ZookeeperConfigBootstrapConfiguration {
@Bean
public ZookeeperPropertySourceLocator zookeeperPropertySourceLocator() {
return new ZookeeperPropertySourceLocator();
}
@Bean
public ZookeeperConfigProperties zookeeperConfigProperties() {
return new ZookeeperConfigProperties();
}
}

View File

@@ -0,0 +1,16 @@
package org.springframework.cloud.zookeeper.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Spencer Gibb
*/
@ConfigurationProperties("zookeeper.config")
@Data
public class ZookeeperConfigProperties {
private boolean enabled = true;
private String root = "config";
}

View File

@@ -0,0 +1,98 @@
package org.springframework.cloud.zookeeper.config;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.TreeCache;
import org.apache.zookeeper.KeeperException.NoNodeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.Lifecycle;
import org.springframework.core.env.EnumerablePropertySource;
import com.google.common.base.Charsets;
/**
* @author Spencer Gibb
*/
public class ZookeeperPropertySource extends EnumerablePropertySource<CuratorFramework>
implements Lifecycle {
private static final Logger LOG = LoggerFactory
.getLogger(ZookeeperPropertySource.class);
private String context;
private TreeCache cache;
private boolean running;
public ZookeeperPropertySource(String context, CuratorFramework source) {
super(context, source);
this.context = context;
if (!this.context.startsWith("/")) {
this.context = "/" + this.context;
}
}
@Override
public void start() {
try {
cache = TreeCache.newBuilder(source, context).build();
cache.start();
running = true;
}
catch (NoNodeException e) {
// no node, ignore
}
catch (Exception e) {
LOG.error("Error initializing ZookeperPropertySource", e);
}
}
@Override
public Object getProperty(String name) {
String fullPath = context + "/" + name.replace(".", "/");
ChildData data = cache.getCurrentData(fullPath);
if (data == null)
return null;
return new String(data.getData(), Charsets.UTF_8);
}
@Override
public String[] getPropertyNames() {
List<String> keys = new ArrayList<>();
findKeys(keys, context);
return keys.toArray(new String[0]);
}
protected void findKeys(List<String> keys, String path) {
Map<String, ChildData> children = cache.getCurrentChildren(path);
if (children == null)
return;
for (Map.Entry<String, ChildData> entry : children.entrySet()) {
ChildData child = entry.getValue();
if (child.getData().length == 0) {
findKeys(keys, child.getPath());
}
else {
keys.add(child.getPath().replace(context + "/", "").replace('/', '.'));
}
}
}
@Override
public void stop() {
cache.close();
running = false;
}
@Override
public boolean isRunning() {
return running;
}
}

View File

@@ -0,0 +1,75 @@
package org.springframework.cloud.zookeeper.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PreDestroy;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.config.client.PropertySourceLocator;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
/**
* @author Spencer Gibb
*/
public class ZookeeperPropertySourceLocator implements PropertySourceLocator {
@Autowired
private ZookeeperConfigProperties properties;
@Autowired
private CuratorFramework curator;
@Override
public PropertySource<?> locate(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
String appName = env.getProperty("spring.application.name");
List<String> profiles = Arrays.asList(env.getActiveProfiles());
String root = properties.getRoot();
List<String> contexts = new ArrayList<>();
String defaultContext = root + "/application";
contexts.add(defaultContext);
addProfiles(contexts, defaultContext, profiles);
String baseContext = root + "/" + appName;
contexts.add(baseContext);
addProfiles(contexts, baseContext, profiles);
CompositePropertySource composite = new CompositePropertySource("zookeeper");
for (String propertySourceContext : contexts) {
ZookeeperPropertySource propertySource = create(propertySourceContext);
propertySource.start();
composite.addPropertySource(propertySource);
// TODO: howto call close when /refresh
}
return composite;
}
return null;
}
@PreDestroy
public void destroy() {
// TODO: call close on zkps's
}
private ZookeeperPropertySource create(String context) {
return new ZookeeperPropertySource(context, curator);
}
private void addProfiles(List<String> contexts, String baseContext,
List<String> profiles) {
for (String profile : profiles) {
contexts.add(baseContext + "::" + profile);
}
}
}

View File

@@ -0,0 +1,3 @@
# Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.zookeeper.config.ZookeeperConfigBootstrapConfiguration

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-zookeeper-core</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Zookeeper Core</name>
<description>Spring Cloud Zookeeper Core</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- Only needed at compile time -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,56 @@
package org.springframework.cloud.zookeeper;
import javax.annotation.PreDestroy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
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;
/**
* @author Spencer Gibb
*/
@Configuration
@EnableConfigurationProperties
public class ZookeeperAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ZookeeperProperties zookeeperProperties() {
return new ZookeeperProperties();
}
@Bean
@ConditionalOnMissingBean
public CuratorFramework curatorFramework() {
CuratorFramework curator = CuratorFrameworkFactory.builder()
// TODO: configurable retry policy
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
// .retryPolicy(new RetryOneTime(100))
// TODO: support ensembleProvider via ExhibitorEnsembleProvider
// .ensembleProvider(new ExhibitorEnsembleProvider())
.connectString(zookeeperProperties().getConnectString()).build();
curator.start();
return curator;
}
@PreDestroy
public void shutdown() {
curatorFramework().close();
}
@Bean
@ConditionalOnMissingBean
public ZookeeperEndpoint zookeeperEndpoint() {
return new ZookeeperEndpoint();
}
@Bean
@ConditionalOnMissingBean
public ZookeeperHealthIndicator zookeeperHealthIndicator() {
return new ZookeeperHealthIndicator();
}
}

View File

@@ -0,0 +1,29 @@
package org.springframework.cloud.zookeeper;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Spencer Gibb
*/
@ConfigurationProperties(prefix = "endpoints.zookeeper", ignoreUnknownFields = false)
public class ZookeeperEndpoint extends AbstractEndpoint<ZookeeperEndpoint.ZookeeperData> {
@Autowired
public ZookeeperEndpoint() {
super("zookeeper", false, true);
}
@Override
public ZookeeperData invoke() {
ZookeeperData data = new ZookeeperData();
return data;
}
@Data
public static class ZookeeperData {
}
}

View File

@@ -0,0 +1,36 @@
package org.springframework.cloud.zookeeper;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
/**
* @author Spencer Gibb
*/
public class ZookeeperHealthIndicator extends AbstractHealthIndicator {
@Autowired
CuratorFramework curator;
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
try {
if (curator.getState() != CuratorFrameworkState.STARTED) {
builder.down().withDetail("error", "Client not started");
}
else if (curator.checkExists().forPath("/") == null) {
builder.down().withDetail("error", "Root for namespace does not exist");
}
else {
builder.up();
}
builder.withDetail("connectionString",
curator.getZookeeperClient().getCurrentConnectionString())
.withDetail("state", curator.getState());
}
catch (Exception e) {
builder.down(e);
}
}
}

View File

@@ -0,0 +1,19 @@
package org.springframework.cloud.zookeeper;
import javax.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Spencer Gibb
*/
@ConfigurationProperties("zookeeper")
@Data
public class ZookeeperProperties {
@NotNull
private String connectString = "localhost:2181";
private boolean enabled = true;
}

View File

@@ -0,0 +1,3 @@
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.zookeeper.ZookeeperAutoConfiguration

View File

@@ -0,0 +1,43 @@
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();
}
}
}

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-zookeeper-discovery</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Zookeeper Discovery</name>
<description>Spring Cloud Zookeeper Discovery</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-core</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- Only needed at compile time -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2013-2014 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.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Configuration;
/**
* @author Dave Syer
*/
@Configuration
@EnableConfigurationProperties
@ConditionalOnBean(SpringClientFactory.class)
@ConditionalOnProperty(value = "ribbon.zookeeper.enabled", matchIfMissing = true)
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = ZookeeperRibbonClientConfiguration.class)
public class RibbonZookeeperAutoConfiguration {
}

View File

@@ -0,0 +1,111 @@
package org.springframework.cloud.zookeeper.discovery;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.transform;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;
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.context.ApplicationContext;
import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
/**
* @author Spencer Gibb
*/
public class ZookeeperDiscoveryClient implements DiscoveryClient {
@Autowired
ApplicationContext context;
@Autowired(required = false)
ServiceInstance<ZookeeperInstance> instance;
@Autowired
ServiceDiscovery<ZookeeperInstance> discovery;
@Override
public String description() {
return "Spring Cloud Zookeeper Discovery Client";
}
@Override
public org.springframework.cloud.client.ServiceInstance getLocalServiceInstance() {
if (instance == null) {
throw new IllegalStateException("Unable to locate instance in zookeeper: "
+ context.getId());
}
return new DefaultServiceInstance(instance.getId(), instance.getAddress(),
instance.getPort());
}
@Override
@SneakyThrows
public List<org.springframework.cloud.client.ServiceInstance> getInstances(
final String serviceId) {
Collection<ServiceInstance<ZookeeperInstance>> zkInstances = discovery
.queryForInstances(serviceId);
Iterable<org.springframework.cloud.client.ServiceInstance> instances = transform(
zkInstances,
new Function<ServiceInstance<ZookeeperInstance>, org.springframework.cloud.client.ServiceInstance>() {
@Nullable
@Override
public org.springframework.cloud.client.ServiceInstance apply(
@Nullable ServiceInstance<ZookeeperInstance> input) {
return new DefaultServiceInstance(serviceId, input.getAddress(),
input.getPort());
}
});
return Lists.newArrayList(instances);
}
@Override
@SneakyThrows
public List<org.springframework.cloud.client.ServiceInstance> getAllInstances() {
Iterable<org.springframework.cloud.client.ServiceInstance> instances = transform(
concat(transform(
discovery.queryForNames(),
new Function<String, Collection<ServiceInstance<ZookeeperInstance>>>() {
@SneakyThrows
public Collection<ServiceInstance<ZookeeperInstance>> apply(
String input) {
return discovery.queryForInstances(input);
}
})),
new Function<ServiceInstance<ZookeeperInstance>, org.springframework.cloud.client.ServiceInstance>() {
public org.springframework.cloud.client.ServiceInstance apply(
ServiceInstance<ZookeeperInstance> input) {
return new DefaultServiceInstance(input.getName(), input
.getAddress(), input.getPort());
}
});
return Lists.newArrayList(instances);
}
@Override
public List<String> getServices() {
ArrayList<String> services = null;
try {
services = Lists.newArrayList(discovery.queryForNames());
}
catch (Exception e) {
Throwables.propagate(e);
}
return services;
}
}

View File

@@ -0,0 +1,70 @@
package org.springframework.cloud.zookeeper.discovery;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
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.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
/**
* @author Spencer Gibb
*/
@Configuration
@EnableConfigurationProperties
public class ZookeeperDiscoveryClientConfiguration {
@Autowired
ApplicationContext context;
@Bean
public ZookeeperDiscoveryProperties zookeeperDiscoveryProperties() {
return new ZookeeperDiscoveryProperties();
}
@Bean
public ZookeeperLifecycle zookeeperLifecycle() {
return new ZookeeperLifecycle();
}
@Bean
public ZookeeperDiscoveryClient zookeeperDiscoveryClient() {
return new ZookeeperDiscoveryClient();
}
@Bean
public ServiceInstance<ZookeeperInstance> serviceInstance() throws Exception {
Environment environment = context.getEnvironment();
Integer port = new Integer(environment.getProperty("server.port", "8080"));
UriSpec uriSpec = new UriSpec(zookeeperDiscoveryProperties().getUriSpec());
return ServiceInstance.<ZookeeperInstance> builder()
.name(environment.getProperty("spring.application.name"))
.payload(new ZookeeperInstance(context.getId())).port(port)
.uriSpec(uriSpec).build();
}
@Bean
public InstanceSerializer<ZookeeperInstance> instanceSerializer() {
return new JsonInstanceSerializer<>(ZookeeperInstance.class);
}
@Bean
public ServiceDiscovery<ZookeeperInstance> serviceDiscovery(CuratorFramework curator)
throws Exception {
return ServiceDiscoveryBuilder.builder(ZookeeperInstance.class).client(curator)
.basePath(zookeeperDiscoveryProperties().getRoot())
.serializer(instanceSerializer()).thisInstance(serviceInstance()).build();
}
@Bean
public ZookeeperDiscoveryHealthIndicator zookeeperDiscoveryHealthIndicator() {
return new ZookeeperDiscoveryHealthIndicator();
}
}

View File

@@ -0,0 +1,41 @@
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.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
/**
* @author Spencer Gibb
*/
@Slf4j
public class ZookeeperDiscoveryHealthIndicator extends AbstractHealthIndicator {
@Autowired
ServiceDiscovery<ZookeeperInstance> serviceDiscovery;
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
try {
Collection<String> names = serviceDiscovery.queryForNames();
ArrayList<ServiceInstance<ZookeeperInstance>> allInstances = new ArrayList<>();
for (String name : names) {
Collection<ServiceInstance<ZookeeperInstance>> instances = serviceDiscovery
.queryForInstances(name);
for (ServiceInstance<ZookeeperInstance> instance : instances) {
allInstances.add(instance);
}
}
builder.up().withDetail("services", allInstances);
}
catch (Exception e) {
log.error("Error", e);
builder.down(e);
}
}
}

View File

@@ -0,0 +1,18 @@
package org.springframework.cloud.zookeeper.discovery;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Spencer Gibb
*/
@ConfigurationProperties("zookeeper.discovery")
@Data
public class ZookeeperDiscoveryProperties {
private boolean enabled = true;
private String root = "/services";
private String uriSpec = "{scheme}://{address}:{port}";
}

View File

@@ -0,0 +1,16 @@
package org.springframework.cloud.zookeeper.discovery;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author Spencer Gibb
*/
@Data
@AllArgsConstructor
public class ZookeeperInstance {
private String id;
private ZookeeperInstance() {
}
}

View File

@@ -0,0 +1,51 @@
package org.springframework.cloud.zookeeper.discovery;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
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.discovery.AbstractDiscoveryLifecycle;
/**
* @author Spencer Gibb
*/
@Slf4j
public class ZookeeperLifecycle extends AbstractDiscoveryLifecycle {
@Autowired
private ZookeeperDiscoveryProperties properties;
@Autowired
private ServiceDiscovery<ZookeeperInstance> serviceDiscovery;
@Autowired
private ServiceInstance<ZookeeperInstance> instance;
@Override
@SneakyThrows
protected void register() {
serviceDiscovery.start();
}
// TODO: implement registerManagement
@Override
@SneakyThrows
protected void deregister() {
serviceDiscovery.unregisterService(instance);
}
// TODO: implement deregisterManagement
@Override
protected boolean isEnabled() {
return properties.isEnabled();
}
@Override
protected Object getConfiguration() {
return properties;
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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;
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;
/**
* Preprocessor that configures defaults for eureka-discovered ribbon clients. Such as:
* <code>@zone</code>, NIWSServerListClassName, DeploymentContextBasedVipAddresses,
* NFLoadBalancerRuleClassName, NIWSServerListFilterClassName and more
*
* @author Spencer Gibb
* @author Dave Syer
*/
@Configuration
public class ZookeeperRibbonClientConfiguration {
protected static final String VALUE_NOT_SET = "__not__set__";
protected static final String DEFAULT_NAMESPACE = "ribbon";
@Autowired
private ServiceDiscovery<ZookeeperInstance> serviceDiscovery;
@Value("${ribbon.client.name}")
private String serviceId = "client";
public ZookeeperRibbonClientConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config) {
ZookeeperServerList serverList = new ZookeeperServerList(serviceDiscovery);
serverList.initWithNiwsConfig(config);
return serverList;
}
@PostConstruct
public void preprocess() {
setProp(this.serviceId, DeploymentContextBasedVipAddresses.key(), this.serviceId);
setProp(this.serviceId, EnableZoneAffinity.key(), "true");
}
protected void setProp(String serviceId, String suffix, String value) {
// how to set the namespace properly?
String key = getKey(serviceId, suffix);
DynamicStringProperty property = getProperty(key);
if (property.get().equals(VALUE_NOT_SET)) {
ConfigurationManager.getConfigInstance().setProperty(key, value);
}
}
protected DynamicStringProperty getProperty(String key) {
return DynamicPropertyFactory.getInstance().getStringProperty(key, VALUE_NOT_SET);
}
protected String getKey(String serviceId, String suffix) {
return serviceId + "." + DEFAULT_NAMESPACE + "." + suffix;
}
}

View File

@@ -0,0 +1,44 @@
package org.springframework.cloud.zookeeper.discovery;
import org.apache.curator.x.discovery.ServiceInstance;
import com.netflix.loadbalancer.Server;
/**
* @author Spencer Gibb
*/
public class ZookeeperServer extends Server {
private final MetaInfo metaInfo;
public ZookeeperServer(final ServiceInstance<ZookeeperInstance> instance) {
// TODO: ssl support
super(instance.getAddress(), instance.getPort());
metaInfo = new MetaInfo() {
@Override
public String getAppName() {
return instance.getName();
}
@Override
public String getServerGroup() {
return null;
}
@Override
public String getServiceIdForDiscovery() {
return null;
}
@Override
public String getInstanceId() {
return instance.getId();
}
};
}
@Override
public MetaInfo getMetaInfo() {
return metaInfo;
}
}

View File

@@ -0,0 +1,73 @@
package org.springframework.cloud.zookeeper.discovery;
import static com.google.common.collect.Collections2.transform;
import static com.google.common.collect.Lists.newArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceInstance;
import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractServerList;
/**
* @author Spencer Gibb
*/
public class ZookeeperServerList extends AbstractServerList<ZookeeperServer> {
private String serviceId;
private final ServiceDiscovery<ZookeeperInstance> serviceDiscovery;
public ZookeeperServerList(ServiceDiscovery<ZookeeperInstance> serviceDiscovery) {
this.serviceDiscovery = serviceDiscovery;
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
this.serviceId = clientConfig.getClientName();
}
@Override
public List<ZookeeperServer> getInitialListOfServers() {
return getServers();
}
@Override
public List<ZookeeperServer> getUpdatedListOfServers() {
return getServers();
}
@SuppressWarnings("unchecked")
private List<ZookeeperServer> getServers() {
try {
Collection<ServiceInstance<ZookeeperInstance>> instances = serviceDiscovery
.queryForInstances(serviceId);
if (instances == null || instances.isEmpty()) {
return Collections.EMPTY_LIST;
}
Collection<ZookeeperServer> servers = transform(instances,
new Function<ServiceInstance<ZookeeperInstance>, ZookeeperServer>() {
@Nullable
@Override
public ZookeeperServer apply(
@Nullable ServiceInstance<ZookeeperInstance> instance) {
ZookeeperServer server = new ZookeeperServer(instance);
return server;
}
});
return newArrayList(servers);
}
catch (Exception e) {
Throwables.propagate(e);
}
return Collections.EMPTY_LIST;
}
}

View File

@@ -0,0 +1,6 @@
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.zookeeper.discovery.RibbonZookeeperAutoConfiguration
org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryClientConfiguration

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-zookeeper-sample</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Zookeeper Sample</name>
<description>Spring Cloud Zookeeper Sample</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<build>
<plugins>
<plugin>
<!--skip deploy -->
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-config</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-discovery</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-bus</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,67 @@
package org.springframework.cloud.zookeeper.sample;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.cloud.bus.jackson.SubtypeModule;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.zookeeper.bus.SimpleRemoteEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Spencer Gibb
*/
@Configuration
@EnableAutoConfiguration
@EnableDiscoveryClient
@RestController
@Slf4j
public class SampleApplication implements ApplicationListener<SimpleRemoteEvent> {
public static final String CLIENT_NAME = "testZookeeperApp";
@Autowired
LoadBalancerClient loadBalancer;
@Autowired
Environment env;
@Autowired(required = false)
RelaxedPropertyResolver resolver;
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
@RequestMapping("/")
public ServiceInstance lb() {
return loadBalancer.choose(CLIENT_NAME);
}
@RequestMapping("/myenv")
public String env(@RequestParam("prop") String prop) {
String property = new RelaxedPropertyResolver(env).getProperty(prop, "Not Found");
return property;
}
@Bean
public SubtypeModule sampleSubtypeModule() {
return new SubtypeModule(SimpleRemoteEvent.class);
}
@Override
public void onApplicationEvent(SimpleRemoteEvent event) {
log.info("Received event: {}", event);
}
}

View File

@@ -0,0 +1,15 @@
server:
port: 8080
#TODO: figure out why I need this here and in bootstrap.yml
spring:
application:
name: testZookeeperApp
endpoints:
restart:
enabled: true
shutdown:
enabled: true
health:
sensitive: false

View File

@@ -0,0 +1,7 @@
spring:
application:
name: testZookeeperApp
cloud:
config:
# TODO: refactor spring-cloud-config to use refresh, etc.. with out config client
enabled: false