diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 6cbcb783..4d735c2f 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -688,6 +688,9 @@ turbine: appConfig: customers ---- +If you need to customize which cluster names should be used by Turbine (you don't want to store cluster names in +`turbine.aggregator.clusterConfig` configuration) provide a bean of type `TurbineClustersProvider`. + The `clusterName` can be customized by a SPEL expression in `turbine.clusterNameExpression` with root an instance of `InstanceInfo`. The default value is `appName`, which means that the Eureka serviceId ends up as the cluster key (i.e. the `InstanceInfo` for customers has an `appName` of "CUSTOMERS"). A different example would be `turbine.clusterNameExpression=aSGName`, which would get the cluster name from the AWS ASG name. Another example: ---- diff --git a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/ConfigurationBasedTurbineClustersProvider.java b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/ConfigurationBasedTurbineClustersProvider.java new file mode 100644 index 00000000..74c5baf6 --- /dev/null +++ b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/ConfigurationBasedTurbineClustersProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2017 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.netflix.turbine; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.List; + +/** + * Provides clusters names for Turbine based on configuration value. + * + * @author Anastasiia Smirnova + */ +public class ConfigurationBasedTurbineClustersProvider implements TurbineClustersProvider { + + private static final Log log = LogFactory.getLog(ConfigurationBasedTurbineClustersProvider.class); + private final TurbineAggregatorProperties properties; + + public ConfigurationBasedTurbineClustersProvider(TurbineAggregatorProperties turbineAggregatorProperties) { + this.properties = turbineAggregatorProperties; + } + + @Override + public List getClusterNames() { + List clusterNames = properties.getClusterConfig(); + log.trace("Using clusters names: " + clusterNames); + return clusterNames; + } +} diff --git a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/EurekaBasedTurbineClustersProvider.java b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/EurekaBasedTurbineClustersProvider.java new file mode 100644 index 00000000..87927510 --- /dev/null +++ b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/EurekaBasedTurbineClustersProvider.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-2017 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.netflix.turbine; + +import com.netflix.discovery.EurekaClient; +import com.netflix.discovery.shared.Application; +import com.netflix.discovery.shared.Applications; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * Provides clusters names for Turbine based on applications names registered in Eureka. + * + * @author Anastasiia Smirnova + */ +public class EurekaBasedTurbineClustersProvider implements TurbineClustersProvider { + + private static final Log log = LogFactory.getLog(EurekaBasedTurbineClustersProvider.class); + private final EurekaClient eurekaClient; + + public EurekaBasedTurbineClustersProvider(EurekaClient eurekaClient) { + this.eurekaClient = eurekaClient; + } + + @Override + public List getClusterNames() { + Applications applications = eurekaClient.getApplications(); + List registeredApplications = applications.getRegisteredApplications(); + List appNames = new ArrayList<>(registeredApplications.size()); + for (Application application : registeredApplications) { + appNames.add(application.getName()); + } + log.trace("Using clusters names: " + appNames); + return appNames; + } +} diff --git a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/SpringAggregatorFactory.java b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/SpringAggregatorFactory.java index badcb9f0..efc53e52 100644 --- a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/SpringAggregatorFactory.java +++ b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/SpringAggregatorFactory.java @@ -16,12 +16,8 @@ package org.springframework.cloud.netflix.turbine; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; -import com.netflix.config.DynamicPropertyFactory; -import com.netflix.config.DynamicStringProperty; import com.netflix.turbine.data.AggDataFromCluster; import com.netflix.turbine.discovery.Instance; import com.netflix.turbine.handler.PerformanceCriteria; @@ -43,8 +39,11 @@ public class SpringAggregatorFactory implements ClusterMonitorFactory clusterMonitor = (ClusterMonitor) findOrRegisterAggregateMonitor(clusterName); clusterMonitor.registerListenertoClusterMonitor(this.StaticListener); try { @@ -87,27 +86,12 @@ public class SpringAggregatorFactory implements ClusterMonitorFactory getClusterNames() { - List clusters = new ArrayList(); - String clusterNames = aggClusters.get(); - if (clusterNames == null || clusterNames.trim().length() == 0) { - clusters.add("default"); - } - else { - String[] parts = aggClusters.get().split(","); - for (String s : parts) { - clusters.add(s); - } - } - return clusters; - } - /** * shutdown all configured cluster monitors */ @Override public void shutdownClusterMonitors() { - for (String clusterName : getClusterNames()) { + for (String clusterName : clustersProvider.getClusterNames()) { ClusterMonitor clusterMonitor = (ClusterMonitor) AggregateClusterMonitor .findOrRegisterAggregateMonitor(clusterName); clusterMonitor.stopMonitor(); diff --git a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineAggregatorProperties.java b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineAggregatorProperties.java new file mode 100644 index 00000000..e7f7e9d2 --- /dev/null +++ b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineAggregatorProperties.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013-2017 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.netflix.turbine; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * @author Anastasiia Smirnova + */ +@ConfigurationProperties("turbine.aggregator") +public class TurbineAggregatorProperties { + + private static final String DEFAULT = "default"; + /** + * The list of cluster names. + */ + private List clusterConfig = Collections.singletonList(DEFAULT); + + public List getClusterConfig() { + return clusterConfig; + } + + public void setClusterConfig(List clusterConfig) { + this.clusterConfig = clusterConfig; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + TurbineAggregatorProperties that = (TurbineAggregatorProperties) o; + return Objects.equals(clusterConfig, that.clusterConfig); + } + + @Override + public int hashCode() { + return Objects.hash(clusterConfig); + } + + @Override + public String toString() { + return "TurbineAggregatorProperties{" + "clusterConfig='" + clusterConfig + '\'' + + '}'; + } +} diff --git a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineClustersProvider.java b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineClustersProvider.java new file mode 100644 index 00000000..1e17cccc --- /dev/null +++ b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineClustersProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2017 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.netflix.turbine; + +import java.util.List; + +/** + * Interface that gives possibility to customize which clusters names Turbine will use. + * + * @author Anastasiia Smirnova + */ +public interface TurbineClustersProvider { + + List getClusterNames(); +} diff --git a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineHttpConfiguration.java b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineHttpConfiguration.java index 26eaf9ca..23ca5969 100644 --- a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineHttpConfiguration.java +++ b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineHttpConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.cloud.netflix.turbine; +import com.netflix.turbine.monitor.cluster.ClusterMonitorFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; @@ -43,18 +44,40 @@ public class TurbineHttpConfiguration { } @Bean + @ConditionalOnMissingBean(name = "turbineStreamServlet") public ServletRegistrationBean turbineStreamServlet() { return new ServletRegistrationBean(new TurbineStreamServlet(), "/turbine.stream"); } @Bean + @ConditionalOnMissingBean public TurbineProperties turbineProperties() { return new TurbineProperties(); } @Bean - public TurbineLifecycle turbineLifecycle(InstanceDiscovery instanceDiscovery) { - return new TurbineLifecycle(instanceDiscovery); + @ConditionalOnMissingBean + public TurbineAggregatorProperties turbineAggregatorProperties() { + return new TurbineAggregatorProperties(); + } + + @Bean + @ConditionalOnMissingBean + public TurbineLifecycle turbineLifecycle(InstanceDiscovery instanceDiscovery, + ClusterMonitorFactory factory) { + return new TurbineLifecycle(instanceDiscovery, factory); + } + + @Bean + @ConditionalOnMissingBean + public ClusterMonitorFactory clusterMonitorFactory(TurbineClustersProvider clustersProvider) { + return new SpringAggregatorFactory(clustersProvider); + } + + @Bean + @ConditionalOnMissingBean + public TurbineClustersProvider clustersProvider(TurbineAggregatorProperties turbineAggregatorProperties) { + return new ConfigurationBasedTurbineClustersProvider(turbineAggregatorProperties); } @Configuration diff --git a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineLifecycle.java b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineLifecycle.java index 1651ef6f..019a8ce7 100644 --- a/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineLifecycle.java +++ b/spring-cloud-netflix-turbine/src/main/java/org/springframework/cloud/netflix/turbine/TurbineLifecycle.java @@ -16,6 +16,7 @@ package org.springframework.cloud.netflix.turbine; +import com.netflix.turbine.monitor.cluster.ClusterMonitorFactory; import org.springframework.context.SmartLifecycle; import org.springframework.core.Ordered; @@ -29,11 +30,13 @@ import com.netflix.turbine.plugins.PluginsFactory; public class TurbineLifecycle implements SmartLifecycle, Ordered { private final InstanceDiscovery instanceDiscovery; + private final ClusterMonitorFactory factory; - private boolean running; + private volatile boolean running; - public TurbineLifecycle(InstanceDiscovery instanceDiscovery) { + public TurbineLifecycle(InstanceDiscovery instanceDiscovery, ClusterMonitorFactory factory) { this.instanceDiscovery = instanceDiscovery; + this.factory = factory; } @Override @@ -48,7 +51,7 @@ public class TurbineLifecycle implements SmartLifecycle, Ordered { @Override public void start() { - PluginsFactory.setClusterMonitorFactory(new SpringAggregatorFactory()); + PluginsFactory.setClusterMonitorFactory(factory); PluginsFactory.setInstanceDiscovery(instanceDiscovery); TurbineInit.init(); } diff --git a/spring-cloud-netflix-turbine/src/test/java/org/springframework/cloud/netflix/turbine/ConfigurationBasedTurbineClustersProviderTest.java b/spring-cloud-netflix-turbine/src/test/java/org/springframework/cloud/netflix/turbine/ConfigurationBasedTurbineClustersProviderTest.java new file mode 100644 index 00000000..5115f342 --- /dev/null +++ b/spring-cloud-netflix-turbine/src/test/java/org/springframework/cloud/netflix/turbine/ConfigurationBasedTurbineClustersProviderTest.java @@ -0,0 +1,32 @@ +package org.springframework.cloud.netflix.turbine; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ConfigurationBasedTurbineClustersProviderTest { + + @Test + public void shouldReturnDefaultClusterIfConfigurationIsEmpty() throws Exception { + TurbineAggregatorProperties properties = new TurbineAggregatorProperties(); + TurbineClustersProvider provider = new ConfigurationBasedTurbineClustersProvider( + properties); + List clusterNames = provider.getClusterNames(); + + assertThat(clusterNames).containsOnly("default"); + } + + @Test + public void shouldReturnConfiguredClusters() throws Exception { + TurbineAggregatorProperties properties = new TurbineAggregatorProperties(); + properties.setClusterConfig(Arrays.asList("cluster1", "cluster2", "cluster3")); + TurbineClustersProvider provider = new ConfigurationBasedTurbineClustersProvider( + properties); + List clusterNames = provider.getClusterNames(); + + assertThat(clusterNames).containsOnly("cluster1", "cluster2", "cluster3"); + } +} \ No newline at end of file diff --git a/spring-cloud-netflix-turbine/src/test/java/org/springframework/cloud/netflix/turbine/EurekaBasedTurbineClustersProviderTest.java b/spring-cloud-netflix-turbine/src/test/java/org/springframework/cloud/netflix/turbine/EurekaBasedTurbineClustersProviderTest.java new file mode 100644 index 00000000..1b7ebcf7 --- /dev/null +++ b/spring-cloud-netflix-turbine/src/test/java/org/springframework/cloud/netflix/turbine/EurekaBasedTurbineClustersProviderTest.java @@ -0,0 +1,42 @@ +package org.springframework.cloud.netflix.turbine; + +import com.netflix.discovery.EurekaClient; +import com.netflix.discovery.shared.Application; +import com.netflix.discovery.shared.Applications; +import org.junit.Test; + +import java.util.List; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class EurekaBasedTurbineClustersProviderTest { + + EurekaClient eurekaClient = mock(EurekaClient.class); + TurbineClustersProvider provider = new EurekaBasedTurbineClustersProvider(eurekaClient); + + @Test + public void shouldProvideAllClustersNames() throws Exception { + Applications applications = registeredApplications(asList(application("service1"), + application("service2"), application("service3"))); + when(eurekaClient.getApplications()).thenReturn(applications); + + List clusterNames = provider.getClusterNames(); + + assertThat(clusterNames).containsOnly("service1", "service2", "service3"); + } + + private Applications registeredApplications(List registered) { + Applications applications = mock(Applications.class); + when(applications.getRegisteredApplications()).thenReturn(registered); + return applications; + } + + private Application application(String name) { + Application application = mock(Application.class); + when(application.getName()).thenReturn(name); + return application; + } +} \ No newline at end of file diff --git a/spring-cloud-netflix-turbine/src/test/java/org/springframework/cloud/netflix/turbine/TurbineAggregatorPropertiesTest.java b/spring-cloud-netflix-turbine/src/test/java/org/springframework/cloud/netflix/turbine/TurbineAggregatorPropertiesTest.java new file mode 100644 index 00000000..80ceb1b2 --- /dev/null +++ b/spring-cloud-netflix-turbine/src/test/java/org/springframework/cloud/netflix/turbine/TurbineAggregatorPropertiesTest.java @@ -0,0 +1,55 @@ +package org.springframework.cloud.netflix.turbine; + +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.util.EnvironmentTestUtils.addEnvironment; + +public class TurbineAggregatorPropertiesTest { + + private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @After + public void clear() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void shouldHaveDefaultConfiguration() throws Exception { + setupContext(); + + TurbineAggregatorProperties actual = getProperties(); + assertThat(actual.getClusterConfig()).containsOnly("default"); + } + + @Test + public void shouldLoadCustomProperties() { + addEnvironment(this.context, + "turbine.aggregator.clusterConfig=cluster1, cluster2, cluster3"); + setupContext(); + + TurbineAggregatorProperties actual = getProperties(); + assertThat(actual.getClusterConfig()).containsOnly("cluster1", "cluster2", + "cluster3"); + } + + private void setupContext() { + this.context.register(TestConfiguration.class); + this.context.refresh(); + } + + private TurbineAggregatorProperties getProperties() { + return this.context.getBean(TurbineAggregatorProperties.class); + } + + @Configuration + @EnableConfigurationProperties(TurbineAggregatorProperties.class) + static class TestConfiguration { + } +} \ No newline at end of file