Commit f14297ce by Spencer Gibb

initial sidecar implementation

parent c8c8b6d0
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
<module>spring-cloud-netflix-eureka-server</module> <module>spring-cloud-netflix-eureka-server</module>
<module>spring-cloud-netflix-turbine</module> <module>spring-cloud-netflix-turbine</module>
<module>spring-cloud-netflix-zuul-server</module> <module>spring-cloud-netflix-zuul-server</module>
<module>spring-cloud-netflix-sidecar</module>
<module>docs</module> <module>docs</module>
</modules> </modules>
...@@ -263,6 +264,20 @@ ...@@ -263,6 +264,20 @@
<turbine.version>1.0.0</turbine.version> <turbine.version>1.0.0</turbine.version>
<zuul.version>1.0.28</zuul.version> <zuul.version>1.0.28</zuul.version>
<netflix.rxjava.version>0.20.7</netflix.rxjava.version> <netflix.rxjava.version>0.20.7</netflix.rxjava.version>
<java.version>1.7</java.version>
</properties> </properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project> </project>
...@@ -2,6 +2,8 @@ package org.springframework.cloud.client.discovery; ...@@ -2,6 +2,8 @@ package org.springframework.cloud.client.discovery;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
import java.util.List;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
*/ */
...@@ -10,4 +12,6 @@ public interface DiscoveryClient { ...@@ -10,4 +12,6 @@ public interface DiscoveryClient {
* @return ServiceInstance with information used to register the local service * @return ServiceInstance with information used to register the local service
*/ */
public ServiceInstance getLocalServiceInstance(); public ServiceInstance getLocalServiceInstance();
public List<ServiceInstance> getInstances(String serviceId);
} }
package org.springframework.cloud.netflix.eureka; package org.springframework.cloud.netflix.eureka;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.netflix.appinfo.InstanceInfo;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.DiscoveryClient;
import javax.annotation.Nullable;
import java.util.List;
import static com.google.common.collect.Iterables.*;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
*/ */
...@@ -12,6 +20,9 @@ public class EurekaDiscoveryClient implements DiscoveryClient { ...@@ -12,6 +20,9 @@ public class EurekaDiscoveryClient implements DiscoveryClient {
@Autowired @Autowired
private EurekaInstanceConfigBean config; private EurekaInstanceConfigBean config;
@Autowired
private com.netflix.discovery.DiscoveryClient discovery;
@Override @Override
public ServiceInstance getLocalServiceInstance() { public ServiceInstance getLocalServiceInstance() {
return new ServiceInstance() { return new ServiceInstance() {
...@@ -31,4 +42,40 @@ public class EurekaDiscoveryClient implements DiscoveryClient { ...@@ -31,4 +42,40 @@ public class EurekaDiscoveryClient implements DiscoveryClient {
} }
}; };
} }
@Override
public List<ServiceInstance> getInstances(String serviceId) {
List<InstanceInfo> infos = discovery.getInstancesByVipAddress(serviceId, false);
Iterable<ServiceInstance> instances = transform(infos, new Function<InstanceInfo, ServiceInstance>() {
@Nullable
@Override
public ServiceInstance apply(@Nullable InstanceInfo info) {
return new EurekaServiceInstance(info);
}
});
return Lists.newArrayList(instances);
}
static class EurekaServiceInstance implements ServiceInstance {
InstanceInfo instance;
EurekaServiceInstance(InstanceInfo instance) {
this.instance = instance;
}
@Override
public String getServiceId() {
return instance.getAppName();
}
@Override
public String getHost() {
return instance.getHostName();
}
@Override
public int getPort() {
return instance.getPort();
}
}
} }
<?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-netflix-sidecar</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Netflix Sidecar</name>
<url>http://projects.spring.io/spring-cloud/</url>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<build>
<plugins>
</plugins>
</build>
<properties>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-metrics-event-stream</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</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-eureka</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.zuul</groupId>
<artifactId>zuul-core</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>
</dependencies>
</project>
package org.springframework.cloud.netflix.sidecar;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author Spencer Gibb
*/
@EnableHystrix
@EnableEurekaClient
@EnableZuulProxy
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(SidecarConfiguration.class)
public @interface EnableSidecar {
}
package org.springframework.cloud.netflix.sidecar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.util.Map;
/**
* @author Spencer Gibb
*/
public class LocalApplicationHealthIndicator extends AbstractHealthIndicator {
@Autowired
SidecarProperties properties;
@SuppressWarnings("unchecked")
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
URI uri = properties.getLocalHealthUri();
if (uri == null) {
builder.up();
return;
}
Map<String, Object> map = new RestTemplate().getForObject(uri, Map.class);
Object status = map.get("status");
if (status != null && status instanceof String) {
builder.status(status.toString());
} else if (status != null && status instanceof Map) {
Map<String, Object> statusMap = (Map<String, Object>) status;
Object code = statusMap.get("code");
if (code != null) {
builder.status(code.toString());
} else {
getWarning(builder);
}
} else {
getWarning(builder);
}
}
private Health.Builder getWarning(Health.Builder builder) {
return builder.unknown().withDetail("warning", "no status field in response");
}
}
package org.springframework.cloud.netflix.sidecar;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Spencer Gibb
*/
@Configuration
@EnableConfigurationProperties
@ConditionalOnExpression("${sidecar.enabled:true}")
public class SidecarConfiguration {
@Bean
public SidecarProperties sidecarProperties() {
return new SidecarProperties();
}
@Bean
public LocalApplicationHealthIndicator localApplicationHealthIndicator() {
return new LocalApplicationHealthIndicator();
}
@Bean
public SidecarController sidecarController() {
return new SidecarController();
}
}
package org.springframework.cloud.netflix.sidecar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author Spencer Gibb
*/
@RestController
public class SidecarController {
@Autowired
DiscoveryClient discovery;
@Value("${spring.application.name}")
String appName;
@RequestMapping("/ping")
public String ping() {
return "OK";
}
@RequestMapping("/hosts")
public List<ServiceInstance> hosts(@RequestParam("appName") String appName) {
List<ServiceInstance> instances = discovery.getInstances(appName);
return instances;
}
@RequestMapping(value = "/", produces = "text/html")
public String home() {
return "<head><title>Sidecar</title></head><body>\n" +
"<a href='/ping'>ping</a><br/>\n" +
"<a href='/health'>health</a><br/>\n" +
"<a href='/hosts?appName="+appName+"'>hosts?appName="+appName+"</a><br/>\n" +
"</body>";
}
}
package org.springframework.cloud.netflix.sidecar;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.net.URI;
/**
* @author Spencer Gibb
*/
@Data
@ConfigurationProperties("sidecar")
public class SidecarProperties {
private URI localHealthUri;
}
package org.springframework.cloud.netflix.sidecar;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableSidecar
@RestController
public class SidecarApplication {
public static void main(String[] args) {
SpringApplication.run(SidecarApplication.class, args);
}
}
package org.springframework.cloud.netflix.sidecar;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SidecarApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
public class SidecarApplicationTests {
@Test
public void contextLoads() {
}
}
server:
port: 5678
spring:
application:
name: sidecarTest
sidecar:
local-health-uri: http://localhost:8081/health
zuul:
proxy:
route:
stores: /stores
customers: /customers
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment