diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 6e39ffb4..33840a1e 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: ---- @@ -830,6 +833,7 @@ To set the `IRule` for a service name `users` you could set the following: ---- users: ribbon: + NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule ---- @@ -1497,9 +1501,39 @@ The location of the backend can be specified as either a "serviceId" url: http://example.com/users_service ---- -These simple url-routes don't get executed as a `HystrixCommand` nor can you loadbalance multiple URLs with Ribbon. -To achieve this, specify a service-route and configure a Ribbon client for the -serviceId (this currently requires disabling Eureka support in Ribbon: +These simple url-routes don't get executed as a `HystrixCommand` nor do they loadbalance multiple URLs with Ribbon. +To achieve this, you can specify a `serviceId` with a static list of servers: + +.application.yml +[source,yaml] +---- +zuul: + routes: + echo: + path: /myusers/** + serviceId: myusers-service + stripPrefix: true + +hystrix: + command: + myusers-service: + execution: + isolation: + thread: + timeoutInMilliseconds: ... + +myusers-service: + ribbon: + NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList + ListOfServers: http://example1.com,http://example2.com + ConnectTimeout: 1000 + ReadTimeout: 3000 + MaxTotalHttpConnections: 500 + MaxConnectionsPerHost: 100 +---- + +Another method is specifiying a service-route and configure a Ribbon client for the +serviceId (this requires disabling Eureka support in Ribbon: see <>), e.g. .application.yml @@ -1697,11 +1731,17 @@ Security. The assumption in this case is that the downstream services might add these headers too, and we want the values from the proxy. To not discard these well known security headers in case Spring Security is on the classpath you can set `zuul.ignoreSecurityHeaders` to `false`. This can be useful if you disabled the HTTP Security response headers in Spring Security and want the values provided by downstream services -=== The Routes Endpoint +=== Management Endpoints -If you are using `@EnableZuulProxy` with tha Spring Boot Actuator you -will enable (by default) an additional endpoint, available via HTTP as -`/routes`. A GET to this endpoint will return a list of the mapped +If you are using `@EnableZuulProxy` with the Spring Boot Actuator you +will enable (by default) two additional endpoints: + +* Routes +* Filters + +==== Routes Endpoint + +A GET to the routes endpoint at `/routes` will return a list of the mapped routes: .GET /routes @@ -1740,6 +1780,12 @@ NOTE: the routes should respond automatically to changes in the service catalog, but the POST to /routes is a way to force the change to happen immediately. +==== Filters Endpoint + +A GET to the filters endpoint at `/filters` will return a map of Zuul +filters by type. For each filter type in the map, you will find a list +of all the filters of that type, along with their details. + === Strangulation Patterns and Local Forwards A common pattern when migrating an existing application or API is to diff --git a/pom.xml b/pom.xml index 83be56bd..4fb9438e 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ 2.0.0.BUILD-SNAPSHOT Elmhurst.BUILD-SNAPSHOT - 1.1.2.RELEASE + 1.1.2.RELEASE 3.6.1 @@ -115,7 +115,7 @@ org.springframework.cloud spring-cloud-contract-dependencies - ${spring-cloud-contract.version} + ${donotreplacespring-cloud-contract.version} pom import diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/FiltersEndpoint.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/FiltersEndpoint.java new file mode 100644 index 00000000..a57d98d0 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/FiltersEndpoint.java @@ -0,0 +1,57 @@ +package org.springframework.cloud.netflix.zuul; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.filters.FilterRegistry; +import org.springframework.boot.actuate.endpoint.AbstractEndpoint; +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedResource; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Endpoint for listing Zuul filters. + * + * @author Daryl Robbins + * @author Gregor Zurowski + */ +@ManagedResource(description = "List Zuul filters") +public class FiltersEndpoint extends AbstractEndpoint>>> { + + private static final String ID = "filters"; + + private final FilterRegistry filterRegistry; + + public FiltersEndpoint(FilterRegistry filterRegistry) { + super(ID, true); + this.filterRegistry = filterRegistry; + } + + @ManagedAttribute + @Override + public Map>> invoke() { + // Map of filters by type + final Map>> filterMap = new TreeMap<>(); + + for (ZuulFilter filter : this.filterRegistry.getAllFilters()) { + // Ensure that we have a list to store filters of each type + if (!filterMap.containsKey(filter.filterType())) { + filterMap.put(filter.filterType(), new ArrayList>()); + } + + final Map filterInfo = new LinkedHashMap<>(); + filterInfo.put("class", filter.getClass().getName()); + filterInfo.put("order", filter.filterOrder()); + filterInfo.put("disabled", filter.isFilterDisabled()); + filterInfo.put("static", filter.isStaticFilter()); + + filterMap.get(filter.filterType()).add(filterInfo); + } + + return filterMap; + } + +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfigurationIntegrationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfigurationIntegrationTests.java index fa546fc0..fc73cfce 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfigurationIntegrationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfigurationIntegrationTests.java @@ -17,9 +17,6 @@ package org.springframework.cloud.netflix.ribbon; -import static org.junit.Assert.assertEquals; - -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -27,6 +24,7 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoCon import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration; import org.springframework.cloud.netflix.ribbon.RibbonAutoConfigurationIntegrationTests.TestConfiguration; +import org.springframework.cloud.netflix.test.TestUtils; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.test.annotation.DirtiesContext; @@ -35,6 +33,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.IClientConfig; +import static org.junit.Assert.assertEquals; + /** * @author Dave Syer */ @@ -48,6 +48,7 @@ public class RibbonAutoConfigurationIntegrationTests { @Test public void serverListIsConfigured() throws Exception { + TestUtils.assumeTestIgnored(RibbonAutoConfigurationIntegrationTests.class); IClientConfig config = this.factory.getClientConfig("client"); assertEquals(25000, config.getPropertyAsInteger(CommonClientConfigKey.ConnectTimeout, 3000)); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/test/TestUtils.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/test/TestUtils.java new file mode 100644 index 00000000..60f6af54 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/test/TestUtils.java @@ -0,0 +1,33 @@ +/* + * 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.test; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assume.assumeThat; + +public class TestUtils { + public static void assumeTestIgnored(Class clazz) { + assumeTestIgnored(clazz.getSimpleName()); + } + + public static void assumeTestIgnored(String name) { + assumeThat("Test ignored", + System.getenv("SPRING_CLOUD_NETFLIX_IGNORE_TESTS"), + not(containsString(name))); + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/FiltersEndpointTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/FiltersEndpointTests.java new file mode 100644 index 00000000..6fc98430 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/FiltersEndpointTests.java @@ -0,0 +1,89 @@ +package org.springframework.cloud.netflix.zuul; + +import com.netflix.zuul.ZuulFilter; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +import static org.hibernate.validator.internal.util.Contracts.assertTrue; +import static org.junit.Assert.assertEquals; + +/** + * Tests for Filters endpoint + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = FiltersEndpointApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + value = { "server.contextPath: /app" }) +public class FiltersEndpointTests { + + @Autowired + private FiltersEndpoint endpoint; + + @Test + public void getFilters() { + final Map>> filters = endpoint.invoke(); + + boolean foundFilter = false; + + if (filters.containsKey("sample")) { + for (Map filterInfo : filters.get("sample")) { + if (TestFilter.class.getName().equals(filterInfo.get("class"))) { + foundFilter = true; + + // Verify filter's attributes + assertEquals(0, filterInfo.get("order")); + + break; // the search is over + } + } + } + + assertTrue(foundFilter, "Could not find expected sample filter from filters endpoint"); + } + +} + +@Configuration +@EnableAutoConfiguration +@RestController +@EnableZuulProxy +class FiltersEndpointApplication { + + @Bean + public ZuulFilter sampleFilter() { + return new TestFilter(); + } + +} + +class TestFilter extends ZuulFilter { + @Override + public String filterType() { + return "sample"; + } + + @Override + public boolean shouldFilter() { + return true; + } + + @Override + public Object run() { + return null; + } + + @Override + public int filterOrder() { + return 0; + } +} \ No newline at end of file diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/CloudEurekaInstanceConfig.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/CloudEurekaInstanceConfig.java index 0156ac8c..6ea3e889 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/CloudEurekaInstanceConfig.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/CloudEurekaInstanceConfig.java @@ -24,5 +24,6 @@ import com.netflix.appinfo.InstanceInfo; */ public interface CloudEurekaInstanceConfig extends EurekaInstanceConfig { void setNonSecurePort(int port); + void setSecurePort(int securePort); InstanceInfo.InstanceStatus getInitialStatus(); } diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java index 8d50f11e..9e5162c6 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java @@ -17,8 +17,6 @@ package org.springframework.cloud.netflix.eureka; -import static org.springframework.cloud.commons.util.IdUtils.getDefaultInstanceId; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -62,7 +60,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Lazy; import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.PropertyResolver; import org.springframework.util.StringUtils; import com.netflix.appinfo.ApplicationInfoManager; @@ -73,6 +70,8 @@ import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs; import com.netflix.discovery.EurekaClient; import com.netflix.discovery.EurekaClientConfig; +import static org.springframework.cloud.commons.util.IdUtils.getDefaultInstanceId; + /** * @author Dave Syer * @author Spencer Gibb @@ -130,13 +129,21 @@ public class EurekaClientAutoConfiguration { public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils) { String hostname = getProperty("eureka.instance.hostname"); boolean preferIpAddress = Boolean.parseBoolean(getProperty("eureka.instance.prefer-ip-address")); + boolean isSecurePortEnabled = Boolean.parseBoolean(getProperty("eureka.instance.secure-port-enabled")); int nonSecurePort = Integer.valueOf(env.getProperty("server.port", env.getProperty("port", "8080"))); + int managementPort = Integer.valueOf(env.getProperty("management.port", String.valueOf(nonSecurePort))); String managementContextPath = env.getProperty("management.context-path", env.getProperty("server.servlet.context-path", "/")); EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils); instance.setNonSecurePort(nonSecurePort); instance.setInstanceId(getDefaultInstanceId(env)); instance.setPreferIpAddress(preferIpAddress); + + if(isSecurePortEnabled) { + int securePort = Integer.valueOf(env.getProperty("server.port", env.getProperty("port", "8080"))); + instance.setSecurePort(securePort); + } + if (managementPort != nonSecurePort && managementPort != 0) { if (StringUtils.hasText(hostname)) { instance.setHostname(hostname); @@ -152,6 +159,7 @@ public class EurekaClientAutoConfiguration { if (StringUtils.hasText(healthCheckUrlPath)) { instance.setHealthCheckUrlPath(healthCheckUrlPath); } + String scheme = instance.getSecurePortEnabled() ? "https" : "http"; try { URL base = new URL(scheme, instance.getHostname(), managementPort, managementContextPath); diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/serviceregistry/EurekaAutoServiceRegistration.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/serviceregistry/EurekaAutoServiceRegistration.java index 817d4a52..984842ef 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/serviceregistry/EurekaAutoServiceRegistration.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/serviceregistry/EurekaAutoServiceRegistration.java @@ -36,6 +36,7 @@ import org.springframework.core.Ordered; * @author Spencer Gibb * @author Jon Schneider * @author Jakub Narloch + * @author raiyan */ public class EurekaAutoServiceRegistration implements AutoServiceRegistration, SmartLifecycle, Ordered { @@ -61,9 +62,15 @@ public class EurekaAutoServiceRegistration implements AutoServiceRegistration, S @Override public void start() { - // only set the port if the nonSecurePort is 0 and this.port != 0 - if (this.port.get() != 0 && this.registration.getNonSecurePort() == 0) { - this.registration.setNonSecurePort(this.port.get()); + // only set the port if the nonSecurePort or securePort is 0 and this.port != 0 + if (this.port.get() != 0) { + if (this.registration.getNonSecurePort() == 0) { + this.registration.setNonSecurePort(this.port.get()); + } + + if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) { + this.registration.setSecurePort(this.port.get()); + } } // only initialize if nonSecurePort is greater than 0 and it isn't already running diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/serviceregistry/EurekaRegistration.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/serviceregistry/EurekaRegistration.java index 7f2adc55..dd9f991a 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/serviceregistry/EurekaRegistration.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/serviceregistry/EurekaRegistration.java @@ -193,6 +193,14 @@ public class EurekaRegistration implements Registration, Closeable { return this.instanceConfig.getNonSecurePort(); } + public void setSecurePort(int port) { + this.instanceConfig.setSecurePort(port); + } + + public int getSecurePort() { + return this.instanceConfig.getSecurePort(); + } + @Override public void close() throws IOException { this.eurekaClient.shutdown(); diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java index 4f1c757a..92456a61 100644 --- a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java +++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java @@ -98,6 +98,23 @@ public class EurekaClientAutoConfigurationTests { this.context.getBeanDefinition("eurekaClient").getFactoryMethodName()); } + @Test + public void securePortPeriods() { + testSecurePort("server.port"); + } + + @Test + public void securePortUnderscores() { + testSecurePort("SERVER_PORT"); + } + + @Test + public void securePort() { + testSecurePort("PORT"); + assertEquals("eurekaClient", + this.context.getBeanDefinition("eurekaClient").getFactoryMethodName()); + } + @Test public void managementPort() { TestPropertyValues.of("server.port=8989", @@ -378,6 +395,13 @@ public class EurekaClientAutoConfigurationTests { assertEquals(8888, getInstanceConfig().getNonSecurePort()); } + private void testSecurePort(String propName) { + EnvironmentTestUtils.addEnvironment(this.context, "eureka.instance.securePortEnabled=true"); + addEnvironment(this.context, propName + ":8443"); + setupContext(); + assertEquals(8443, getInstanceConfig().getSecurePort()); + } + private EurekaInstanceConfigBean getInstanceConfig() { return this.context.getBean(EurekaInstanceConfigBean.class); } diff --git a/spring-cloud-netflix-hystrix-contract/pom.xml b/spring-cloud-netflix-hystrix-contract/pom.xml index d0a97efe..6f6532d2 100644 --- a/spring-cloud-netflix-hystrix-contract/pom.xml +++ b/spring-cloud-netflix-hystrix-contract/pom.xml @@ -15,7 +15,7 @@ Spring Cloud Netflix Hystrix Contract ${basedir}/.. - 1.1.2.RELEASE + 1.1.2.RELEASE @@ -30,7 +30,7 @@ org.springframework.cloud spring-cloud-contract-verifier - ${spring-cloud-contract.version} + ${donotreplacespring-cloud-contract.version} diff --git a/spring-cloud-netflix-hystrix-stream/pom.xml b/spring-cloud-netflix-hystrix-stream/pom.xml index 0a479d81..fc507095 100644 --- a/spring-cloud-netflix-hystrix-stream/pom.xml +++ b/spring-cloud-netflix-hystrix-stream/pom.xml @@ -101,7 +101,7 @@ org.springframework.cloud spring-cloud-contract-maven-plugin - ${spring-cloud-contract.version} + ${donotreplacespring-cloud-contract.version} true 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 diff --git a/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyAutoConfiguration.java b/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyAutoConfiguration.java index e35d7d3d..75f7d862 100644 --- a/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyAutoConfiguration.java +++ b/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyAutoConfiguration.java @@ -57,6 +57,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import com.netflix.zuul.filters.FilterRegistry; + /** * @author Spencer Gibb * @author Dave Syer @@ -158,17 +160,24 @@ public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration { @Configuration @ConditionalOnClass(Health.class) - protected static class RoutesEndpointConfiguration { + protected static class EndpointConfiguration { @Autowired(required = false) private TraceRepository traces; @Bean @ConditionalOnEnabledEndpoint - public RoutesEndpoint zuulEndpoint(RouteLocator routeLocator) { + public RoutesEndpoint routesEndpoint(RouteLocator routeLocator) { return new RoutesEndpoint(routeLocator); } + @ConditionalOnEnabledEndpoint + @Bean + public FiltersEndpoint filtersEndpoint() { + FilterRegistry filterRegistry = FilterRegistry.instance(); + return new FiltersEndpoint(filterRegistry); + } + @Bean public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) { TraceProxyRequestHelper helper = new TraceProxyRequestHelper(); diff --git a/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/RoutesEndpointDetailsTests.java b/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/RoutesEndpointDetailsTests.java index e99af749..7559fe02 100644 --- a/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/RoutesEndpointDetailsTests.java +++ b/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/RoutesEndpointDetailsTests.java @@ -31,6 +31,9 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.netflix.zuul.RoutesEndpoint; +import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent; +import org.springframework.cloud.netflix.zuul.RoutesMvcEndpoint; import org.springframework.cloud.netflix.zuul.filters.Route; import org.springframework.cloud.netflix.zuul.filters.RouteLocator; import org.springframework.context.ApplicationEventPublisher; diff --git a/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/RoutesEndpointIntegrationTests.java b/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/RoutesEndpointIntegrationTests.java index 16be8998..d4263f35 100644 --- a/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/RoutesEndpointIntegrationTests.java +++ b/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/RoutesEndpointIntegrationTests.java @@ -25,6 +25,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.cloud.netflix.zuul.EnableZuulProxy; +import org.springframework.cloud.netflix.zuul.RoutesEndpoint; +import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Configuration; import org.springframework.core.ParameterizedTypeReference; diff --git a/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/RoutesEndpointTests.java b/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/RoutesEndpointTests.java index ffe2de8f..3cd93d67 100644 --- a/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/RoutesEndpointTests.java +++ b/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/RoutesEndpointTests.java @@ -27,6 +27,7 @@ import java.util.Map; import org.junit.Before; import org.junit.Test; +import org.springframework.cloud.netflix.zuul.RoutesEndpoint; import org.springframework.cloud.netflix.zuul.filters.Route; import org.springframework.cloud.netflix.zuul.filters.RouteLocator;