From fa88a50daecffe5b511019cc91598ab70a462500 Mon Sep 17 00:00:00 2001 From: wenxuan70 Date: Mon, 25 Sep 2023 10:17:08 +0800 Subject: [PATCH] feat: add circuit breaker actuator (#1136) --- CHANGELOG.md | 1 + .../pom.xml | 12 +++ .../PolarisCircuitBreakerEndpoint.java | 86 +++++++++++++++++++ ...rcuitBreakerEndpointAutoConfiguration.java | 49 +++++++++++ .../main/resources/META-INF/spring.factories | 3 +- .../PolarisCircuitBreakerEndpointTest.java | 84 ++++++++++++++++++ .../polaris/context/ServiceRuleManager.java | 17 +++- 7 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpoint.java create mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpointAutoConfiguration.java create mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpointTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 95aa1263e..241575198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,3 +7,4 @@ - [Refactoring:remove invalid @AutoConfigureAfter and @AutoConfigureBefore from discovery client automatic configuration.](https://github.com/Tencent/spring-cloud-tencent/pull/1115) - [feat: support log path configuration parameters.](https://github.com/Tencent/spring-cloud-tencent/pull/1128) - [refactor:optimize the order and condition matching of service registration automatic configuration.](https://github.com/Tencent/spring-cloud-tencent/pull/1129) +- [feat: add circuit breaker actuator.](https://github.com/Tencent/spring-cloud-tencent/pull/1136) \ No newline at end of file diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml b/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml index 58a68a422..230e50a82 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml @@ -96,6 +96,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 000000000..b2ff0eeff --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpoint.java @@ -0,0 +1,86 @@ +/* + * 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.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; + +/** + * 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() { + CircuitBreakerProto.CircuitBreaker circuitBreaker = serviceRuleManager.getServiceCircuitBreakerRule( + MetadataContext.LOCAL_NAMESPACE, + MetadataContext.LOCAL_SERVICE + ); + + Map polarisCircuitBreakerInfo = new HashMap<>(); + + polarisCircuitBreakerInfo.put("namespace", MetadataContext.LOCAL_NAMESPACE); + polarisCircuitBreakerInfo.put("service", MetadataContext.LOCAL_SERVICE); + polarisCircuitBreakerInfo.put("circuitBreakerRules", parseCircuitBreakerRule(circuitBreaker)); + + return polarisCircuitBreakerInfo; + } + + private List parseCircuitBreakerRule(CircuitBreakerProto.CircuitBreaker circuitBreaker) { + List circuitBreakerRuleList = new ArrayList<>(); + + for (CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule : circuitBreaker.getRulesList()) { + String ruleJson; + try { + ruleJson = JsonFormat.printer().print(circuitBreakerRule); + } + catch (InvalidProtocolBufferException e) { + LOG.error("rule to Json failed. check rule {}.", circuitBreakerRule, e); + throw new RuntimeException("Json failed.", e); + } + circuitBreakerRuleList.add(JacksonUtils.deserialize2Map(ruleJson)); + } + + return circuitBreakerRuleList; + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpointAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpointAutoConfiguration.java new file mode 100644 index 000000000..6c9073925 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpointAutoConfiguration.java @@ -0,0 +1,49 @@ +/* + * 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 com.tencent.cloud.polaris.circuitbreaker.config.ConditionalOnPolarisCircuitBreakerEnabled; +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.ConditionalOnBean; +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 CircuitBreaker's Endpoint. + * + * @author wenxuan70 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(Endpoint.class) +@ConditionalOnPolarisCircuitBreakerEnabled +public class PolarisCircuitBreakerEndpointAutoConfiguration { + + @Bean + @ConditionalOnBean(ServiceRuleManager.class) + @ConditionalOnMissingBean + @ConditionalOnAvailableEndpoint + public PolarisCircuitBreakerEndpoint polarisCircuitBreakerEndpoint(ServiceRuleManager serviceRuleManager) { + return new PolarisCircuitBreakerEndpoint(serviceRuleManager); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories index a873ed563..07972a65c 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories @@ -2,6 +2,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration,\ com.tencent.cloud.polaris.circuitbreaker.config.ReactivePolarisCircuitBreakerAutoConfiguration,\ com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration,\ - com.tencent.cloud.polaris.circuitbreaker.config.GatewayPolarisCircuitBreakerAutoConfiguration + com.tencent.cloud.polaris.circuitbreaker.config.GatewayPolarisCircuitBreakerAutoConfiguration,\ + com.tencent.cloud.polaris.circuitbreaker.endpoint.PolarisCircuitBreakerEndpointAutoConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerBootstrapConfiguration diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpointTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpointTest.java new file mode 100644 index 000000000..23913957e --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpointTest.java @@ -0,0 +1,84 @@ +/* + * 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.Map; + +import com.google.protobuf.StringValue; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.cloud.polaris.context.ServiceRuleManager; +import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto; +import com.tencent.polaris.specification.api.v1.model.ModelProto; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test for {@link PolarisCircuitBreakerEndpoint}. + * + * @author wenxuan70 + */ +@ExtendWith(MockitoExtension.class) +public class PolarisCircuitBreakerEndpointTest { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withBean(ApplicationContextAwareUtils.class) + .withPropertyValues("spring.cloud.polaris.namespace=" + NAMESPACE_TEST) + .withPropertyValues("spring.cloud.polaris.service=" + SERVICE_PROVIDER); + + private ServiceRuleManager serviceRuleManager; + + @BeforeEach + void setUp() { + serviceRuleManager = mock(ServiceRuleManager.class); + when(serviceRuleManager.getServiceCircuitBreakerRule(anyString(), anyString())).thenAnswer(invocation -> { + CircuitBreakerProto.CircuitBreakerRule.Builder ruleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder(); + ruleBuilder.setName("test_for_circuit_breaker"); + ruleBuilder.setEnable(true); + ruleBuilder.setLevel(CircuitBreakerProto.Level.METHOD); + CircuitBreakerProto.RuleMatcher.Builder rmBuilder = CircuitBreakerProto.RuleMatcher.newBuilder(); + rmBuilder.setDestination(CircuitBreakerProto.RuleMatcher.DestinationService.newBuilder().setNamespace("default").setService("svc2").setMethod( + ModelProto.MatchString.newBuilder().setValue(StringValue.newBuilder().setValue("*").build()).build()).build()); + rmBuilder.setSource(CircuitBreakerProto.RuleMatcher.SourceService.newBuilder().setNamespace("*").setService("*").build()); + ruleBuilder.setRuleMatcher(rmBuilder.build()); + return CircuitBreakerProto.CircuitBreaker.newBuilder().addRules(ruleBuilder.build()).build(); + }); + } + + @Test + public void testPolarisCircuitBreaker() { + contextRunner.run(context -> { + PolarisCircuitBreakerEndpoint endpoint = new PolarisCircuitBreakerEndpoint(serviceRuleManager); + Map circuitBreakerInfo = endpoint.circuitBreaker(); + assertThat(circuitBreakerInfo).isNotNull(); + assertThat(circuitBreakerInfo.get("namespace")).isNotNull(); + assertThat(circuitBreakerInfo.get("service")).isNotNull(); + assertThat(circuitBreakerInfo.get("circuitBreakerRules")).asList().isNotEmpty(); + }); + } +} 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 128899491..826d4fbe2 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,20 @@ public class ServiceRuleManager { return rules; } + public CircuitBreakerProto.CircuitBreaker getServiceCircuitBreakerRule(String namespace, String service) { + LOG.debug("Get service circuit breaker rules with namespace:{} and service:{}.", namespace, service); + + ServiceRule serviceRule = getServiceRule(namespace, service, ServiceEventKey.EventType.CIRCUIT_BREAKING); + if (serviceRule != null) { + Object rule = serviceRule.getRule(); + if (rule instanceof CircuitBreakerProto.CircuitBreaker) { + return (CircuitBreakerProto.CircuitBreaker) rule; + } + } + + return null; + } + private ServiceRule getServiceRule(String namespace, String service, ServiceEventKey.EventType eventType) { GetServiceRuleRequest getServiceRuleRequest = new GetServiceRuleRequest(); getServiceRuleRequest.setRuleType(eventType);