diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6aa6f19c..fb04f01d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,3 +12,4 @@
- [fix: dynamic routing using cookies.](https://github.com/Tencent/spring-cloud-tencent/pull/1152)
- [fix:fix retry loadbalancer not working bug.](https://github.com/Tencent/spring-cloud-tencent/pull/1157)
- [fix:fix header validation when using Chinese char.](https://github.com/Tencent/spring-cloud-tencent/pull/1167)
+- [feat: add circuit breaker actuator.](https://github.com/Tencent/spring-cloud-tencent/pull/1172)
diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml b/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml
index 31743b0c..6a760d13 100644
--- a/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml
+++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml
@@ -103,6 +103,18 @@
+
+ org.springframework.boot
+ spring-boot-actuator
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-actuator-autoconfigure
+ true
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpoint.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpoint.java
new file mode 100644
index 00000000..f9ada512
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpoint.java
@@ -0,0 +1,90 @@
+/*
+ * 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.circuitbreaker.endpoint;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+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.api.utils.CollectionUtils;
+import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
+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;
+
+/**
+ * Endpoint of polaris circuit breaker, include circuit breaker rules.
+ *
+ * @author wenxuan70
+ */
+@Endpoint(id = "polaris-circuit-breaker")
+public class PolarisCircuitBreakerEndpoint {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PolarisCircuitBreakerEndpoint.class);
+
+ private final ServiceRuleManager serviceRuleManager;
+
+ public PolarisCircuitBreakerEndpoint(ServiceRuleManager serviceRuleManager) {
+ this.serviceRuleManager = serviceRuleManager;
+ }
+
+ @ReadOperation
+ public Map circuitBreaker(@Selector String dstService) {
+ List rules = serviceRuleManager.getServiceCircuitBreakerRule(
+ MetadataContext.LOCAL_NAMESPACE,
+ MetadataContext.LOCAL_SERVICE,
+ dstService
+ );
+
+ Map polarisCircuitBreakerInfo = new HashMap<>();
+
+ polarisCircuitBreakerInfo.put("namespace", MetadataContext.LOCAL_NAMESPACE);
+ polarisCircuitBreakerInfo.put("service", MetadataContext.LOCAL_SERVICE);
+
+ List
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/resources/bootstrap.yml
index f71696fc..99b0aebc 100644
--- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/resources/bootstrap.yml
+++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/resources/bootstrap.yml
@@ -20,3 +20,9 @@ logging:
root: info
com.tencent.cloud: debug
+management:
+ endpoints:
+ web:
+ exposure:
+ include:
+ - polaris-circuit-breaker
diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/pom.xml
index 342df6ae..b31f84d4 100644
--- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/pom.xml
+++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/pom.xml
@@ -48,6 +48,11 @@
org.springframework.cloud
spring-cloud-starter-gateway
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/resources/bootstrap.yml
index 3ea326fa..1c946649 100644
--- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/resources/bootstrap.yml
+++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/resources/bootstrap.yml
@@ -57,3 +57,9 @@ logging:
com.tencent.polaris.plugins.registry: off
com.tencent.cloud: debug
+management:
+ endpoints:
+ web:
+ exposure:
+ include:
+ - polaris-circuit-breaker
diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/pom.xml
index dc59e660..4aabcb4d 100644
--- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/pom.xml
+++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/pom.xml
@@ -33,6 +33,11 @@
com.tencent.cloud
spring-cloud-starter-tencent-polaris-circuitbreaker
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/resources/bootstrap.yml
index 4687ea9d..9cd52b81 100644
--- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/resources/bootstrap.yml
+++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/resources/bootstrap.yml
@@ -18,3 +18,9 @@ logging:
root: info
com.tencent.cloud: debug
+management:
+ endpoints:
+ web:
+ exposure:
+ include:
+ - polaris-circuit-breaker
diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/pom.xml
index efb1089a..497be7ad 100644
--- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/pom.xml
+++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/pom.xml
@@ -33,6 +33,11 @@
com.tencent.cloud
spring-cloud-starter-tencent-polaris-circuitbreaker
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/resources/bootstrap.yml
index a6b162df..9c6f22fd 100644
--- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/resources/bootstrap.yml
+++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/resources/bootstrap.yml
@@ -18,3 +18,9 @@ logging:
root: info
com.tencent.cloud: debug
+management:
+ endpoints:
+ web:
+ exposure:
+ include:
+ - polaris-circuit-breaker
diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/ServiceRuleManager.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/ServiceRuleManager.java
index 12889949..abf93219 100644
--- a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/ServiceRuleManager.java
+++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/ServiceRuleManager.java
@@ -27,13 +27,14 @@ import com.tencent.polaris.api.pojo.ServiceRule;
import com.tencent.polaris.api.rpc.GetServiceRuleRequest;
import com.tencent.polaris.api.rpc.ServiceRuleResponse;
import com.tencent.polaris.client.api.SDKContext;
+import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto;
import com.tencent.polaris.specification.api.v1.traffic.manage.RoutingProto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * the manager of service governance rules. for example: rate limit rule, router rules.
+ * the manager of service governance rules. for example: rate limit rule, router rules, circuit breaker rules.
*
* @author lepdou 2022-05-13
*/
@@ -88,6 +89,32 @@ public class ServiceRuleManager {
return rules;
}
+ public List getServiceCircuitBreakerRule(String namespace, String sourceService, String dstService) {
+ LOG.debug("Get service circuit breaker rules with namespace:{} and sourceService:{} and dstService:{}.", namespace, sourceService, dstService);
+
+ List rules = new ArrayList<>();
+
+ // get source service circuit breaker rules.
+ ServiceRule sourceServiceRule = getServiceRule(namespace, sourceService, ServiceEventKey.EventType.CIRCUIT_BREAKING);
+ if (sourceServiceRule != null) {
+ Object rule = sourceServiceRule.getRule();
+ if (rule instanceof CircuitBreakerProto.CircuitBreaker) {
+ rules.addAll(((CircuitBreakerProto.CircuitBreaker) rule).getRulesList());
+ }
+ }
+
+ // get peer service circuit breaker rules.
+ ServiceRule dstServiceRule = getServiceRule(namespace, dstService, ServiceEventKey.EventType.CIRCUIT_BREAKING);
+ if (dstServiceRule != null) {
+ Object rule = dstServiceRule.getRule();
+ if (rule instanceof CircuitBreakerProto.CircuitBreaker) {
+ rules.addAll(((CircuitBreakerProto.CircuitBreaker) rule).getRulesList());
+ }
+ }
+
+ return rules;
+ }
+
private ServiceRule getServiceRule(String namespace, String service, ServiceEventKey.EventType eventType) {
GetServiceRuleRequest getServiceRuleRequest = new GetServiceRuleRequest();
getServiceRuleRequest.setRuleType(eventType);
diff --git a/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/ServiceRuleManagerTest.java b/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/ServiceRuleManagerTest.java
new file mode 100644
index 00000000..43c61d3b
--- /dev/null
+++ b/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/ServiceRuleManagerTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.context;
+
+import java.util.List;
+
+import com.tencent.polaris.api.core.ConsumerAPI;
+import com.tencent.polaris.api.pojo.ServiceEventKey;
+import com.tencent.polaris.api.pojo.ServiceRule;
+import com.tencent.polaris.api.rpc.ServiceRuleResponse;
+import com.tencent.polaris.client.api.SDKContext;
+import com.tencent.polaris.client.pojo.ServiceRuleByProto;
+import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
+import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto;
+import com.tencent.polaris.specification.api.v1.traffic.manage.RoutingProto;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test for {@link ServiceRuleManager}.
+ *
+ * @author wenxuan70
+ */
+@ExtendWith(MockitoExtension.class)
+public class ServiceRuleManagerTest {
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private SDKContext sdkContext;
+
+ @Mock
+ private ConsumerAPI consumerAPI;
+
+ @BeforeEach
+ public void setUp() {
+ when(sdkContext.getConfig().getGlobal().getAPI().getTimeout()).thenReturn(500L);
+ }
+
+ @Test
+ public void testGetServiceCircuitBreakerRule() {
+ final String testNamespace = "testNamespace";
+ final String testSourceService = "testSourceService";
+ final String testDstService = "testDstService";
+
+ CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder()
+ .addRules(CircuitBreakerProto.CircuitBreakerRule.newBuilder().build())
+ .build();
+ ServiceRuleByProto serviceRule = new ServiceRuleByProto(circuitBreaker,
+ "111",
+ false,
+ ServiceEventKey.EventType.CIRCUIT_BREAKING);
+ ServiceRuleResponse serviceRuleResponse = new ServiceRuleResponse(serviceRule);
+
+ // source
+ when(consumerAPI.getServiceRule(
+ argThat(request -> request != null
+ && testNamespace.equals(request.getNamespace())
+ && testSourceService.equals(request.getService())
+ && ServiceEventKey.EventType.CIRCUIT_BREAKING.equals(request.getRuleType()))
+ )).thenReturn(serviceRuleResponse);
+
+ ServiceRuleResponse emptyRuleResponse = new ServiceRuleResponse(null);
+
+ // destination
+ when(consumerAPI.getServiceRule(
+ argThat(request -> request != null
+ && testNamespace.equals(request.getNamespace())
+ && testDstService.equals(request.getService())
+ && ServiceEventKey.EventType.CIRCUIT_BREAKING.equals(request.getRuleType()))
+ )).thenReturn(emptyRuleResponse);
+
+ ServiceRuleManager serviceRuleManager = new ServiceRuleManager(sdkContext, consumerAPI);
+ List serviceCircuitBreakerRule = serviceRuleManager.getServiceCircuitBreakerRule(testNamespace,
+ testSourceService,
+ testDstService);
+
+ assertThat(serviceCircuitBreakerRule).hasSize(1);
+ }
+
+ @Test
+ public void testGetServiceRouterRule() {
+ final String testNamespace = "testNamespace";
+ final String testSourceService = "testSourceService";
+ final String testDstService = "testDstService";
+
+ RoutingProto.Routing routing = RoutingProto.Routing.newBuilder()
+ .addOutbounds(RoutingProto.Route.newBuilder().build())
+ .build();
+ ServiceRule serviceRule = new ServiceRuleByProto(routing,
+ "111",
+ false,
+ ServiceEventKey.EventType.ROUTING);
+ ServiceRuleResponse serviceRuleResponse = new ServiceRuleResponse(serviceRule);
+
+ // source
+ when(consumerAPI.getServiceRule(
+ argThat(request -> request != null
+ && testNamespace.equals(request.getNamespace())
+ && testSourceService.equals(request.getService())
+ && ServiceEventKey.EventType.ROUTING.equals(request.getRuleType()))
+ )).thenReturn(serviceRuleResponse);
+
+
+ ServiceRuleResponse emptyRuleResponse = new ServiceRuleResponse(null);
+
+ // destination
+ when(consumerAPI.getServiceRule(
+ argThat(request -> request != null
+ && testNamespace.equals(request.getNamespace())
+ && testDstService.equals(request.getService())
+ && ServiceEventKey.EventType.ROUTING.equals(request.getRuleType()))
+ )).thenReturn(emptyRuleResponse);
+
+ ServiceRuleManager serviceRuleManager = new ServiceRuleManager(sdkContext, consumerAPI);
+ List serviceRouterRule = serviceRuleManager.getServiceRouterRule(testNamespace,
+ testSourceService,
+ testDstService);
+
+ assertThat(serviceRouterRule).hasSize(1);
+ }
+
+ @Test
+ public void testGetServiceRateLimitRule() {
+ final String testNamespace = "testNamespace";
+ final String testService = "testService";
+
+ RateLimitProto.RateLimit rateLimit = RateLimitProto.RateLimit.getDefaultInstance();
+ ServiceRule serviceRule = new ServiceRuleByProto(rateLimit,
+ "111",
+ false,
+ ServiceEventKey.EventType.ROUTING);
+ ServiceRuleResponse serviceRuleResponse = new ServiceRuleResponse(serviceRule);
+
+ when(consumerAPI.getServiceRule(
+ argThat(request -> request != null
+ && testNamespace.equals(request.getNamespace())
+ && testService.equals(request.getService())
+ && ServiceEventKey.EventType.RATE_LIMITING.equals(request.getRuleType()))
+ )).thenReturn(serviceRuleResponse);
+
+ ServiceRuleManager serviceRuleManager = new ServiceRuleManager(sdkContext, consumerAPI);
+ RateLimitProto.RateLimit rateLimitRule = serviceRuleManager.getServiceRateLimitRule(testNamespace, testService);
+
+ assertThat(rateLimitRule).isNotNull();
+ }
+}