diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 401aea7c..a715542f 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -306,7 +306,7 @@ To run the Hystrix Dashboard annotate your Spring Boot main class with `@EnableH Looking at an individual instances Hystrix data is not very useful in terms of the overall health of the system. https://github.com/Netflix/Turbine[Turbine] is an application that aggregates all of the relevant `/hystrix.stream` endpoints into a combined `/turbine.stream` for use in the Hystrix Dashboard. Individual instances are located via Eureka. Running Turbine is as simple as annotating your main class with the `@EnableTurbine` annotation. -`turbine.appConfig` is a list of eureka serviceId's that turbine will use to lookup instances. `turbine.aggregator.clusterConfig` is used to group instances together. This comes from the eureka `InstanceInfo`. It is currently hard coded to `InstanceInfo.getAppName()`. The turbine stream is then used in the Hystrix dashboard using a url that looks like: http://my.turbine.sever:8080/turbine.stream?cluster=CUSTOMERS +`turbine.appConfig` is a list of eureka serviceId's that turbine will use to lookup instances. `turbine.aggregator.clusterConfig` is used to group instances together. This comes from the eureka `InstanceInfo`. The clusterName is a SPEL expression evaluated against the InstanceInfo. The default clusterNameExpression is `appName`. The turbine stream is then used in the Hystrix dashboard using a url that looks like: http://my.turbine.sever:8080/turbine.stream?cluster=CUSTOMERS The `cluster` parameter must match an entry in `turbine.aggregator.clusterConfig`. @@ -317,6 +317,8 @@ turbine: appConfig: customers ---- +The clusterName can be customized by a SPEL expression in `turbine.clusterNameExpression`. For example, `turbine.clusterNameExpression=aSGName` would get the clustername from the AWS ASG name. + == Declarative REST Client: Feign https://github.com/Netflix/feign[Feign] is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same `HttpMessageConverters` used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka to provide a load balanced http client when using Feign. 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 9dc081be..0c72d839 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 @@ -3,7 +3,6 @@ package org.springframework.cloud.netflix.eureka; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean; /** * @author Spencer Gibb diff --git a/spring-cloud-netflix-turbine/pom.xml b/spring-cloud-netflix-turbine/pom.xml index e9ff164c..d2d8351b 100644 --- a/spring-cloud-netflix-turbine/pom.xml +++ b/spring-cloud-netflix-turbine/pom.xml @@ -38,5 +38,11 @@ com.netflix.turbine turbine-core + + org.projectlombok + lombok + + provided + diff --git a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/EurekaInstanceDiscovery.java b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/EurekaInstanceDiscovery.java index cd610b1d..a8e201e6 100644 --- a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/EurekaInstanceDiscovery.java +++ b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/EurekaInstanceDiscovery.java @@ -12,6 +12,9 @@ import com.netflix.turbine.discovery.Instance; import com.netflix.turbine.discovery.InstanceDiscovery; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; import java.util.*; @@ -29,10 +32,15 @@ public class EurekaInstanceDiscovery implements InstanceDiscovery { // Property the controls the list of applications that are enabled in Eureka private static final DynamicStringProperty ApplicationList = DynamicPropertyFactory.getInstance().getStringProperty("turbine.appConfig", ""); - public EurekaInstanceDiscovery() { + private final Expression clusterNameExpression; + + public EurekaInstanceDiscovery(TurbineProperties turbineProperties) { // Eureka client should already be configured by spring-platform-netflix-core // initialize eureka client. //DiscoveryManager.getInstance().initComponent(new MyDataCenterInstanceConfig(), new DefaultEurekaClientConfig()); + + SpelExpressionParser parser = new SpelExpressionParser(); + clusterNameExpression = parser.parseExpression(turbineProperties.getClusterNameExpression()); } /** @@ -162,10 +170,12 @@ public class EurekaInstanceDiscovery implements InstanceDiscovery { * @return */ protected String getClusterName(InstanceInfo iInfo) { - //TODO: make ASG configurable using app name for demo. //return iInfo.getASGName(); - //AppGroupName is UPPERCASE from eureka - //return iInfo.getAppGroupName(); - return iInfo.getAppName(); + StandardEvaluationContext context = new StandardEvaluationContext(iInfo); + Object value = clusterNameExpression.getValue(context); + if (value != null) { + return value.toString(); + } + return null; } /** diff --git a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineConfiguration.java b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineConfiguration.java index 1c535f80..0431ddd8 100644 --- a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineConfiguration.java +++ b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineConfiguration.java @@ -5,6 +5,7 @@ import com.netflix.turbine.init.TurbineInit; import com.netflix.turbine.plugins.PluginsFactory; import com.netflix.turbine.streaming.servlet.TurbineStreamServlet; import org.springframework.boot.context.embedded.ServletRegistrationBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.Bean; @@ -15,6 +16,7 @@ import org.springframework.core.Ordered; * @author Spencer Gibb */ @Configuration +@EnableConfigurationProperties @EnableEurekaClient public class TurbineConfiguration implements SmartLifecycle, Ordered { @@ -23,9 +25,14 @@ public class TurbineConfiguration implements SmartLifecycle, Ordered { return new ServletRegistrationBean(new TurbineStreamServlet(), "/turbine.stream"); } + @Bean + public TurbineProperties turbineProperties() { + return new TurbineProperties(); + } + @Bean public InstanceDiscovery instanceDiscovery() { - return new EurekaInstanceDiscovery(); + return new EurekaInstanceDiscovery(turbineProperties()); } private boolean running; diff --git a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineProperties.java b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineProperties.java new file mode 100644 index 00000000..cb461364 --- /dev/null +++ b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineProperties.java @@ -0,0 +1,13 @@ +package org.springframework.cloud.netflix.turbine; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Spencer Gibb + */ +@Data +@ConfigurationProperties("turbine") +public class TurbineProperties { + private String clusterNameExpression = "appName"; +} diff --git a/spring-cloud-netflix-turbine/src/test/java/org/springframework/cloud/netflix/turbine/EurekaInstanceDiscoveryTest.java b/spring-cloud-netflix-turbine/src/test/java/org/springframework/cloud/netflix/turbine/EurekaInstanceDiscoveryTest.java new file mode 100644 index 00000000..b2685258 --- /dev/null +++ b/spring-cloud-netflix-turbine/src/test/java/org/springframework/cloud/netflix/turbine/EurekaInstanceDiscoveryTest.java @@ -0,0 +1,38 @@ +package org.springframework.cloud.netflix.turbine; + +import static org.junit.Assert.*; + +import com.netflix.appinfo.InstanceInfo; +import org.junit.Test; + +/** + * @author Spencer Gibb + */ +public class EurekaInstanceDiscoveryTest { + + + @Test + public void testGetClusterName() { + String appName = "testAppName"; + EurekaInstanceDiscovery discovery = new EurekaInstanceDiscovery(new TurbineProperties()); + InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder() + .setAppName(appName) + .build(); + String clusterName = discovery.getClusterName(instanceInfo); + assertEquals("clusterName is wrong", appName.toUpperCase(), clusterName); + } + + @Test + public void testGetClusterNameCustomExpression() { + TurbineProperties turbineProperties = new TurbineProperties(); + turbineProperties.setClusterNameExpression("aSGName"); + EurekaInstanceDiscovery discovery = new EurekaInstanceDiscovery(turbineProperties); + String asgName = "myAsgName"; + InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder() + .setAppName("testApp") + .setASGName(asgName) + .build(); + String clusterName = discovery.getClusterName(instanceInfo); + assertEquals("clusterName is wrong", asgName, clusterName); + } +}