Merge branch '3.0.x'

This commit is contained in:
spencergibb
2021-10-28 16:02:39 -04:00
5 changed files with 212 additions and 0 deletions

View File

@@ -2119,6 +2119,10 @@ spring:
For some usages of the gateway, properties are adequate, but some production use cases benefit from loading configuration from an external source, such as a database. Future milestone versions will have `RouteDefinitionLocator` implementations based off of Spring Data Repositories, such as Redis, MongoDB, and Cassandra.
=== RouteDefinition Metrics
To enable `RouteDefinition` metrics, add spring-boot-starter-actuator as a project dependency. Then, by default, the metrics will be available as long as the property `spring.cloud.gateway.metrics.enabled` is set to `true`. A gauge metric named `spring.cloud.gateway.routes.count` will be added, whose value is the number of `RouteDefinitions`. This metric will be available from `/actuator/metrics/spring.cloud.gateway.routes.count`.
== Route Metadata Configuration
You can configure additional parameters for each route by using metadata, as follows:

View File

@@ -30,6 +30,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayMetricsFilter;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionMetrics;
import org.springframework.cloud.gateway.support.tagsprovider.GatewayHttpTagsProvider;
import org.springframework.cloud.gateway.support.tagsprovider.GatewayRouteTagsProvider;
import org.springframework.cloud.gateway.support.tagsprovider.GatewayTagsProvider;
@@ -71,4 +73,12 @@ public class GatewayMetricsAutoConfiguration {
return new GatewayMetricsFilter(meterRegistry, tagsProviders, properties.getPrefix());
}
@Bean
@ConditionalOnBean(MeterRegistry.class)
@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".metrics.enabled", matchIfMissing = true)
public RouteDefinitionMetrics routeDefinitionMetrics(MeterRegistry meterRegistry,
RouteDefinitionLocator routeDefinitionLocator, GatewayMetricsProperties properties) {
return new RouteDefinitionMetrics(meterRegistry, routeDefinitionLocator, properties.getPrefix());
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2013-2020 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
*
* https://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.gateway.route;
import java.util.concurrent.atomic.AtomicInteger;
import io.micrometer.core.instrument.MeterRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.GatewayMetricsFilter;
import org.springframework.context.ApplicationListener;
/**
* A metric to track the number of routes definitions on the gateway.
*
* @author Fredrich Ombico
*/
public class RouteDefinitionMetrics implements ApplicationListener<RefreshRoutesEvent> {
private static final Log log = LogFactory.getLog(GatewayMetricsFilter.class);
private final RouteDefinitionLocator routeLocator;
private final AtomicInteger routeDefinitionCount;
private final String metricsPrefix;
public RouteDefinitionMetrics(MeterRegistry meterRegistry, RouteDefinitionLocator routeLocator,
String metricsPrefix) {
this.routeLocator = routeLocator;
if (metricsPrefix.endsWith(".")) {
this.metricsPrefix = metricsPrefix.substring(0, metricsPrefix.length() - 1);
}
else {
this.metricsPrefix = metricsPrefix;
}
routeDefinitionCount = meterRegistry.gauge(this.metricsPrefix + ".routes.count", new AtomicInteger(0));
}
public String getMetricsPrefix() {
return metricsPrefix;
}
@Override
public void onApplicationEvent(RefreshRoutesEvent event) {
routeLocator.getRouteDefinitions().count().subscribe(count -> {
routeDefinitionCount.set(count.intValue());
if (log.isDebugEnabled()) {
log.debug("New routes count: " + routeDefinitionCount);
}
});
}
}

View File

@@ -28,6 +28,7 @@ import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.filter.GatewayMetricsFilter;
import org.springframework.cloud.gateway.route.RouteDefinitionMetrics;
import org.springframework.cloud.gateway.support.tagsprovider.GatewayTagsProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.junit4.SpringRunner;
@@ -48,6 +49,9 @@ public class GatewayMetricsAutoConfigurationTests {
@Autowired(required = false)
private GatewayMetricsFilter filter;
@Autowired(required = false)
private RouteDefinitionMetrics routeDefinitionMetrics;
@Autowired(required = false)
private List<GatewayTagsProvider> tagsProviders;
@@ -58,6 +62,12 @@ public class GatewayMetricsAutoConfigurationTests {
assertThat(tagsProviders).isNotEmpty();
}
@Test
public void routeDefinitionMetricsBeanExists() {
assertThat(routeDefinitionMetrics).isNotNull();
assertThat(routeDefinitionMetrics.getMetricsPrefix()).isEqualTo("spring.cloud.gateway");
}
}
@RunWith(SpringRunner.class)
@@ -67,11 +77,19 @@ public class GatewayMetricsAutoConfigurationTests {
@Autowired(required = false)
private GatewayMetricsFilter filter;
@Autowired(required = false)
private RouteDefinitionMetrics routeDefinitionMetrics;
@Test
public void gatewayMetricsBeanMissing() {
assertThat(filter).isNull();
}
@Test
public void routeDefinitionMetricsBeanMissing() {
assertThat(routeDefinitionMetrics).isNull();
}
}
@RunWith(SpringRunner.class)
@@ -82,6 +100,9 @@ public class GatewayMetricsAutoConfigurationTests {
@Autowired(required = false)
private GatewayMetricsFilter filter;
@Autowired(required = false)
private RouteDefinitionMetrics routeDefinitionMetrics;
@Autowired(required = false)
private List<GatewayTagsProvider> tagsProviders;
@@ -92,6 +113,12 @@ public class GatewayMetricsAutoConfigurationTests {
assertThat(tagsProviders).extracting("class").contains(CustomTagsProviderConfig.EmptyTagsProvider.class);
}
@Test
public void routeDefinitionMetricsBeanExists() {
assertThat(routeDefinitionMetrics).isNotNull();
assertThat(routeDefinitionMetrics.getMetricsPrefix()).isEqualTo("myprefix");
}
}
@SpringBootConfiguration

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2013-2020 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
*
* https://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.gateway.route;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import io.micrometer.core.instrument.MeterRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
/**
* @author Fredrich Ombico
*/
@ExtendWith(MockitoExtension.class)
class RouteDefinitionMetricsTests {
@Mock
private MeterRegistry registry;
@Mock
private RouteDefinitionLocator routeDefinitionLocator;
private RouteDefinitionMetrics routeDefinitionMetrics;
private AtomicInteger routeDefinitionCount;
@BeforeEach
void setUp() {
routeDefinitionCount = new AtomicInteger(0);
when(registry.gauge(any(String.class), any(AtomicInteger.class))).thenReturn(routeDefinitionCount);
routeDefinitionMetrics = new RouteDefinitionMetrics(registry, routeDefinitionLocator, "prefix.");
}
@Test
void metricsPrefix() {
assertThat(routeDefinitionMetrics.getMetricsPrefix()).isEqualTo("prefix");
}
@Test
void shouldReportOneRoute() {
List<RouteDefinition> oneRoute = Collections.singletonList(new RouteDefinition());
when(routeDefinitionLocator.getRouteDefinitions()).thenReturn(Flux.fromStream(oneRoute.stream()));
RefreshRoutesEvent refreshRoutesEvent = new RefreshRoutesEvent(this);
routeDefinitionMetrics.onApplicationEvent(refreshRoutesEvent);
assertThat(routeDefinitionCount.get()).isEqualTo(1);
}
@Test
void shouldReportMultipleRoutes() {
List<RouteDefinition> multipleRoutes = Arrays.asList(new RouteDefinition(), new RouteDefinition(),
new RouteDefinition(), new RouteDefinition(), new RouteDefinition());
when(routeDefinitionLocator.getRouteDefinitions()).thenReturn(Flux.fromStream(multipleRoutes.stream()));
RefreshRoutesEvent refreshRoutesEvent = new RefreshRoutesEvent(this);
routeDefinitionMetrics.onApplicationEvent(refreshRoutesEvent);
assertThat(routeDefinitionCount.get()).isEqualTo(5);
}
@Test
void shouldReportZeroIfNoRoutes() {
RouteDefinition[] zeroRoutes = new RouteDefinition[0];
when(routeDefinitionLocator.getRouteDefinitions()).thenReturn(Flux.fromArray(zeroRoutes));
RefreshRoutesEvent refreshRoutesEvent = new RefreshRoutesEvent(this);
routeDefinitionMetrics.onApplicationEvent(refreshRoutesEvent);
assertThat(routeDefinitionCount.get()).isEqualTo(0);
}
}