diff --git a/pom.xml b/pom.xml index dd8ec313..ce7c0ee4 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ spring-cloud-netflix-hystrix-dashboard spring-cloud-netflix-eureka-server spring-cloud-netflix-turbine + spring-cloud-netflix-sidecar docs @@ -253,10 +254,21 @@ + + 0.6.3 + 1.1.145 + 6.1.3 + 1.4.0-RC5 + 2.0-RC13 + 1.0.0 + 1.0.28 + 0.20.7 + 1.7 + + - org.apache.maven.plugins maven-compiler-plugin 3.1 @@ -267,15 +279,4 @@ - - 0.6.3 - 1.1.145 - 6.1.3 - 1.4.0-RC5 - 2.0-RC13 - 1.0.0 - 1.0.28 - 0.20.7 - - diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java index 2cd7da29..084f6f70 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java @@ -13,5 +13,7 @@ public interface DiscoveryClient { */ public ServiceInstance getLocalServiceInstance(); + public List getInstances(String serviceId); + public List getServices(); } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaDiscoveryClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaDiscoveryClient.java index 05aef595..c4508f63 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaDiscoveryClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaDiscoveryClient.java @@ -46,6 +46,42 @@ public class EurekaDiscoveryClient implements DiscoveryClient { }; } + @Override + public List getInstances(String serviceId) { + List infos = discovery.getInstancesByVipAddress(serviceId, false); + Iterable instances = transform(infos, new Function() { + @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(); + } + } + @Override public List getServices() { Applications applications = discovery.getApplications(); diff --git a/spring-cloud-netflix-sidecar/pom.xml b/spring-cloud-netflix-sidecar/pom.xml new file mode 100644 index 00000000..ad30ed54 --- /dev/null +++ b/spring-cloud-netflix-sidecar/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + spring-cloud-netflix-sidecar + jar + Spring Cloud Netflix Sidecar + http://projects.spring.io/spring-cloud/ + + org.springframework.cloud + spring-cloud-netflix + 1.0.0.BUILD-SNAPSHOT + .. + + + + + + + + + + + + + org.springframework.cloud + spring-cloud-netflix-core + + + com.netflix.eureka + eureka-client + + + com.netflix.hystrix + hystrix-core + + + com.netflix.hystrix + hystrix-metrics-event-stream + + + com.netflix.hystrix + hystrix-javanica + + + com.netflix.ribbon + ribbon + + + com.netflix.ribbon + ribbon-core + + + com.netflix.ribbon + ribbon-eureka + + + com.netflix.ribbon + ribbon-httpclient + + + com.netflix.zuul + zuul-core + + + org.projectlombok + lombok + + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + diff --git a/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/EnableSidecar.java b/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/EnableSidecar.java new file mode 100644 index 00000000..a4400808 --- /dev/null +++ b/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/EnableSidecar.java @@ -0,0 +1,21 @@ +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 { +} diff --git a/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/LocalApplicationHealthIndicator.java b/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/LocalApplicationHealthIndicator.java new file mode 100644 index 00000000..a0b5095b --- /dev/null +++ b/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/LocalApplicationHealthIndicator.java @@ -0,0 +1,49 @@ +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 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 statusMap = (Map) 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"); + } +} diff --git a/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarConfiguration.java b/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarConfiguration.java new file mode 100644 index 00000000..99b48b83 --- /dev/null +++ b/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarConfiguration.java @@ -0,0 +1,30 @@ +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(); + } +} diff --git a/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarController.java b/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarController.java new file mode 100644 index 00000000..52d59f5a --- /dev/null +++ b/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarController.java @@ -0,0 +1,44 @@ +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 hosts(@RequestParam("appName") String appName) { + List instances = discovery.getInstances(appName); + return instances; + } + + @RequestMapping(value = "/", produces = "text/html") + public String home() { + return "Sidecar\n" + + "ping
\n" + + "health
\n" + + "hosts?appName="+appName+"
\n" + + ""; + } +} diff --git a/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarProperties.java b/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarProperties.java new file mode 100644 index 00000000..052c9c98 --- /dev/null +++ b/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarProperties.java @@ -0,0 +1,15 @@ +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; +} diff --git a/spring-cloud-netflix-sidecar/src/test/java/org/springframework/cloud/netflix/sidecar/SidecarApplication.java b/spring-cloud-netflix-sidecar/src/test/java/org/springframework/cloud/netflix/sidecar/SidecarApplication.java new file mode 100644 index 00000000..956559fa --- /dev/null +++ b/spring-cloud-netflix-sidecar/src/test/java/org/springframework/cloud/netflix/sidecar/SidecarApplication.java @@ -0,0 +1,14 @@ +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); + } +} diff --git a/spring-cloud-netflix-sidecar/src/test/java/org/springframework/cloud/netflix/sidecar/SidecarApplicationTests.java b/spring-cloud-netflix-sidecar/src/test/java/org/springframework/cloud/netflix/sidecar/SidecarApplicationTests.java new file mode 100644 index 00000000..ee3fdf5a --- /dev/null +++ b/spring-cloud-netflix-sidecar/src/test/java/org/springframework/cloud/netflix/sidecar/SidecarApplicationTests.java @@ -0,0 +1,20 @@ +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() { + } + +} diff --git a/spring-cloud-netflix-sidecar/src/test/resources/application.yml b/spring-cloud-netflix-sidecar/src/test/resources/application.yml new file mode 100644 index 00000000..14912394 --- /dev/null +++ b/spring-cloud-netflix-sidecar/src/test/resources/application.yml @@ -0,0 +1,15 @@ +server: + port: 5678 +spring: + application: + name: sidecarTest + +sidecar: + local-health-uri: http://localhost:8081/health + +zuul: + proxy: + route: + stores: /stores + customers: /customers +