add router actuator endpoint. (#519)

Co-authored-by: lepdou <ledouzhang@tencent.com>
pull/525/head
Haotian Zhang 2 years ago committed by GitHub
parent f0035d3f69
commit e1e3ab0060
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -17,3 +17,4 @@
- [Fix the code analysis error.](https://github.com/Tencent/spring-cloud-tencent/pull/499) - [Fix the code analysis error.](https://github.com/Tencent/spring-cloud-tencent/pull/499)
- [Optimize router label resolver spi](https://github.com/Tencent/spring-cloud-tencent/pull/503) - [Optimize router label resolver spi](https://github.com/Tencent/spring-cloud-tencent/pull/503)
- [Fix typo & Code optimization](https://github.com/Tencent/spring-cloud-tencent/pull/511) - [Fix typo & Code optimization](https://github.com/Tencent/spring-cloud-tencent/pull/511)
- [Feature: Add router actuate endpoint](https://github.com/Tencent/spring-cloud-tencent/pull/519)

@ -13,7 +13,6 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * 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 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.config.endpoint; package com.tencent.cloud.polaris.config.endpoint;

@ -13,7 +13,6 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * 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 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.config.endpoint; package com.tencent.cloud.polaris.config.endpoint;

@ -39,14 +39,13 @@ import org.springframework.cloud.client.discovery.DiscoveryClient;
* @author shuiqingliu * @author shuiqingliu
*/ */
@Endpoint(id = "polaris-discovery") @Endpoint(id = "polaris-discovery")
public class PolarisDiscoveryEndPoint { public class PolarisDiscoveryEndpoint {
private final PolarisDiscoveryProperties polarisDiscoveryProperties; private final PolarisDiscoveryProperties polarisDiscoveryProperties;
private final DiscoveryClient polarisDiscoveryClient; private final DiscoveryClient polarisDiscoveryClient;
private final PolarisDiscoveryHandler polarisDiscoveryHandler; private final PolarisDiscoveryHandler polarisDiscoveryHandler;
public PolarisDiscoveryEndPoint(PolarisDiscoveryProperties polarisDiscoveryProperties, public PolarisDiscoveryEndpoint(PolarisDiscoveryProperties polarisDiscoveryProperties, DiscoveryClient polarisDiscoveryClient, PolarisDiscoveryHandler polarisDiscoveryHandler) {
DiscoveryClient polarisDiscoveryClient, PolarisDiscoveryHandler polarisDiscoveryHandler) {
this.polarisDiscoveryProperties = polarisDiscoveryProperties; this.polarisDiscoveryProperties = polarisDiscoveryProperties;
this.polarisDiscoveryClient = polarisDiscoveryClient; this.polarisDiscoveryClient = polarisDiscoveryClient;
this.polarisDiscoveryHandler = polarisDiscoveryHandler; this.polarisDiscoveryHandler = polarisDiscoveryHandler;
@ -77,7 +76,6 @@ public class PolarisDiscoveryEndPoint {
private ServiceInstances getServiceInstances(String serviceId) { private ServiceInstances getServiceInstances(String serviceId) {
InstancesResponse instancesResponse = polarisDiscoveryHandler.getHealthyInstances(serviceId); InstancesResponse instancesResponse = polarisDiscoveryHandler.getHealthyInstances(serviceId);
ServiceInstances serviceInstances = instancesResponse.toServiceInstances(); return instancesResponse.toServiceInstances();
return serviceInstances;
} }
} }

@ -42,8 +42,8 @@ public class PolarisDiscoveryEndpointAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint @ConditionalOnAvailableEndpoint
public PolarisDiscoveryEndPoint polarisDiscoveryEndPoint(PolarisDiscoveryProperties polarisDiscoveryProperties, public PolarisDiscoveryEndpoint polarisDiscoveryEndPoint(PolarisDiscoveryProperties polarisDiscoveryProperties,
DiscoveryClient discoveryClient, PolarisDiscoveryHandler polarisDiscoveryHandler) { DiscoveryClient discoveryClient, PolarisDiscoveryHandler polarisDiscoveryHandler) {
return new PolarisDiscoveryEndPoint(polarisDiscoveryProperties, discoveryClient, polarisDiscoveryHandler); return new PolarisDiscoveryEndpoint(polarisDiscoveryProperties, discoveryClient, polarisDiscoveryHandler);
} }
} }

@ -14,7 +14,6 @@
* CONDITIONS OF ANY KIND, either express or implied. See the License for the * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*/ */
package com.tencent.cloud.polaris.endpoint; package com.tencent.cloud.polaris.endpoint;
import java.util.Map; import java.util.Map;
@ -45,13 +44,13 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author shuiqingliu * @author shuiqingliu
*/ */
public class PolarisDiscoveryEndPointTest { public class PolarisDiscoveryEndpointTest {
private static NamingServer namingServer; private static NamingServer namingServer;
private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of( .withConfiguration(AutoConfigurations.of(
PolarisDiscoveryEndPointTest.PolarisPropertiesConfiguration.class, PolarisPropertiesConfiguration.class,
PolarisDiscoveryClientConfiguration.class, PolarisDiscoveryClientConfiguration.class,
PolarisDiscoveryAutoConfiguration.class, PolarisDiscoveryAutoConfiguration.class,
PolarisDiscoveryEndpointAutoConfiguration.class)) PolarisDiscoveryEndpointAutoConfiguration.class))
@ -81,10 +80,9 @@ public class PolarisDiscoveryEndPointTest {
DiscoveryClient discoveryClient = context DiscoveryClient discoveryClient = context
.getBean(PolarisDiscoveryClient.class); .getBean(PolarisDiscoveryClient.class);
PolarisDiscoveryHandler polarisDiscoveryHandler = context.getBean(PolarisDiscoveryHandler.class); PolarisDiscoveryHandler polarisDiscoveryHandler = context.getBean(PolarisDiscoveryHandler.class);
PolarisDiscoveryEndPoint polarisDiscoveryEndPoint = PolarisDiscoveryEndpoint polarisDiscoveryEndpoint = new PolarisDiscoveryEndpoint(polarisDiscoveryProperties, discoveryClient, polarisDiscoveryHandler);
new PolarisDiscoveryEndPoint(polarisDiscoveryProperties, discoveryClient, polarisDiscoveryHandler);
Map<String, Object> mapInfo = polarisDiscoveryEndPoint.polarisDiscovery("java_provider_test"); Map<String, Object> mapInfo = polarisDiscoveryEndpoint.polarisDiscovery("java_provider_test");
assertThat(polarisDiscoveryProperties).isEqualTo(mapInfo.get("PolarisDiscoveryProperties")); assertThat(polarisDiscoveryProperties).isEqualTo(mapInfo.get("PolarisDiscoveryProperties"));
}); });

@ -24,19 +24,16 @@ import java.util.Map;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat; import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.polaris.client.pb.RateLimitProto; import com.tencent.polaris.client.pb.RateLimitProto;
import com.tencent.polaris.client.pb.RoutingProto;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
/** /**
@ -58,19 +55,17 @@ public class PolarisRateLimitRuleEndpoint {
} }
@ReadOperation @ReadOperation
public Map<String, Object> rateLimit(@Selector String namespace, @Selector String service, @Nullable String dstService) { public Map<String, Object> rateLimit() {
RateLimitProto.RateLimit rateLimit = serviceRuleManager.getServiceRateLimitRule(MetadataContext.LOCAL_NAMESPACE,
MetadataContext.LOCAL_SERVICE);
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
RateLimitProto.RateLimit rateLimit = serviceRuleManager.getServiceRateLimitRule(namespace, service);
result.put("properties", polarisRateLimitProperties); result.put("properties", polarisRateLimitProperties);
result.put("namespace", namespace); result.put("namespace", MetadataContext.LOCAL_NAMESPACE);
result.put("service", service); result.put("service", MetadataContext.LOCAL_SERVICE);
result.put("rateLimits", parseRateLimitRule(rateLimit)); result.put("rateLimitRules", parseRateLimitRule(rateLimit));
if (StringUtils.isEmpty(dstService)) {
return result;
}
List<RoutingProto.Route> routes = serviceRuleManager.getServiceRouterRule(namespace, service, dstService);
result.put("routes", routes);
return result; return result;
} }

@ -97,7 +97,7 @@ public class PolarisRateLimitRuleEndpointTests {
public void testPolarisRateLimit() { public void testPolarisRateLimit() {
this.contextRunner.run(context -> polarisRateLimitProperties = context.getBean(PolarisRateLimitProperties.class)); this.contextRunner.run(context -> polarisRateLimitProperties = context.getBean(PolarisRateLimitProperties.class));
PolarisRateLimitRuleEndpoint polarisRateLimitRuleEndpoint = new PolarisRateLimitRuleEndpoint(serviceRuleManager, polarisRateLimitProperties); PolarisRateLimitRuleEndpoint polarisRateLimitRuleEndpoint = new PolarisRateLimitRuleEndpoint(serviceRuleManager, polarisRateLimitProperties);
Map<String, Object> rateLimit = polarisRateLimitRuleEndpoint.rateLimit("namespaceTest", "TestApp2", "TestApp3"); Map<String, Object> rateLimit = polarisRateLimitRuleEndpoint.rateLimit();
assertThat(polarisRateLimitProperties).isEqualTo(rateLimit.get("properties")); assertThat(polarisRateLimitProperties).isEqualTo(rateLimit.get("properties"));
} }

@ -70,6 +70,18 @@
</dependency> </dependency>
<!-- spring cloud gateway dependencies end --> <!-- spring cloud gateway dependencies end -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>

@ -0,0 +1,107 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.cloud.polaris.router.endpoint;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.RoutingProto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.util.CollectionUtils;
/**
* Router actuator endpoint.
* @author lepdou 2022-07-25
*/
@Endpoint(id = "polaris-router")
public class PolarisRouterEndpoint {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisRouterEndpoint.class);
private final ServiceRuleManager serviceRuleManager;
public PolarisRouterEndpoint(ServiceRuleManager serviceRuleManager) {
this.serviceRuleManager = serviceRuleManager;
}
@ReadOperation
public Map<String, Object> router(@Selector String dstService) {
List<RoutingProto.Route> routerRules = serviceRuleManager.getServiceRouterRule(MetadataContext.LOCAL_NAMESPACE,
MetadataContext.LOCAL_SERVICE, dstService);
Map<String, Object> result = new HashMap<>();
List<Object> rules = new LinkedList<>();
result.put("routerRules", rules);
if (CollectionUtils.isEmpty(routerRules)) {
return result;
}
for (RoutingProto.Route route : routerRules) {
rules.add(parseRouterRule(route));
}
return result;
}
private Object parseRouterRule(RoutingProto.Route routeRule) {
Map<String, Object> result = new HashMap<>();
List<RoutingProto.Source> sourcePbs = routeRule.getSourcesList();
List<Object> sources = new LinkedList<>();
for (RoutingProto.Source sourcePb : sourcePbs) {
sources.add(pb2Json(sourcePb));
}
result.put("sources", sources);
List<RoutingProto.Destination> destPbs = routeRule.getDestinationsList();
List<Object> destinations = new LinkedList<>();
for (RoutingProto.Destination destPb : destPbs) {
destinations.add(pb2Json(destPb));
}
result.put("destinations", destinations);
return result;
}
private Object pb2Json(MessageOrBuilder pbObject) {
String jsonStr;
try {
jsonStr = JsonFormat.printer().print(pbObject);
}
catch (InvalidProtocolBufferException e) {
String msg = "parse router rule to json error.";
LOGGER.error(msg, e);
throw new RuntimeException(msg, e);
}
return JacksonUtils.deserialize2Map(jsonStr);
}
}

@ -0,0 +1,46 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.cloud.polaris.router.endpoint;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* The AutoConfiguration for polaris router endpoint.
*
* @author lepdou 2022-07-25
**/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Endpoint.class)
@ConditionalOnPolarisEnabled
public class PolarisRouterEndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint
public PolarisRouterEndpoint polarisRouterEndpoint(ServiceRuleManager serviceRuleManager) {
return new PolarisRouterEndpoint(serviceRuleManager);
}
}

@ -1,3 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.router.config.RouterAutoConfiguration,\ com.tencent.cloud.polaris.router.config.RouterAutoConfiguration,\
com.tencent.cloud.polaris.router.config.FeignAutoConfiguration com.tencent.cloud.polaris.router.config.FeignAutoConfiguration,\
com.tencent.cloud.polaris.router.endpoint.PolarisRouterEndpointAutoConfiguration

@ -0,0 +1,117 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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 com.tencent.cloud.polaris.router.endpoint;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RoutingProto;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* Test for {@link PolarisRouterEndpoint}
* @author lepdou 2022-07-25
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisRouterEndpointTest {
private static final String testDestService = "dstService";
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@Mock
private ServiceRuleManager serviceRuleManager;
@InjectMocks
private PolarisRouterEndpoint polarisRouterEndpoint;
@BeforeClass
public static void beforeClass() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn(testDestService);
}
@AfterClass
public static void afterClass() {
mockedApplicationContextAwareUtils.close();
}
@Test
public void testHasRouterRule() {
Map<String, ModelProto.MatchString> labels = new HashMap<>();
ModelProto.MatchString matchString = ModelProto.MatchString.getDefaultInstance();
String validKey1 = "${http.header.uid}";
String validKey2 = "${http.query.name}";
String validKey3 = "${http.method}";
String validKey4 = "${http.uri}";
String validKey5 = "${http.body.customkey}";
String invalidKey = "$http.expression.wrong}";
labels.put(validKey1, matchString);
labels.put(validKey2, matchString);
labels.put(validKey3, matchString);
labels.put(validKey4, matchString);
labels.put(validKey5, matchString);
labels.put(invalidKey, matchString);
RoutingProto.Source source1 = RoutingProto.Source.newBuilder().putAllMetadata(labels).build();
RoutingProto.Source source2 = RoutingProto.Source.newBuilder().putAllMetadata(labels).build();
RoutingProto.Source source3 = RoutingProto.Source.newBuilder().putAllMetadata(new HashMap<>()).build();
List<RoutingProto.Route> routes = new LinkedList<>();
RoutingProto.Route route = RoutingProto.Route.newBuilder()
.addAllSources(Lists.newArrayList(source1, source2, source3))
.build();
routes.add(route);
when(serviceRuleManager.getServiceRouterRule(testDestService, testDestService, testDestService)).thenReturn(routes);
Map<String, Object> actuator = polarisRouterEndpoint.router(testDestService);
Assert.assertNotNull(actuator.get("routerRules"));
Assert.assertEquals(1, ((List) actuator.get("routerRules")).size());
}
@Test
public void testHasNotRouterRule() {
List<RoutingProto.Route> routes = new LinkedList<>();
when(serviceRuleManager.getServiceRouterRule(testDestService, testDestService, testDestService)).thenReturn(routes);
Map<String, Object> actuator = polarisRouterEndpoint.router(testDestService);
Assert.assertNotNull(actuator.get("routerRules"));
Assert.assertEquals(0, ((List) actuator.get("routerRules")).size());
}
}

@ -34,6 +34,16 @@
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId> <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

@ -14,3 +14,9 @@ spring:
enabled: true enabled: true
loadbalancer: loadbalancer:
enabled: true enabled: true
management:
endpoints:
web:
exposure:
include:
- polaris-router

Loading…
Cancel
Save