From fdec06660adc9310ef935f70aca577f1b4b5ff9c Mon Sep 17 00:00:00 2001 From: Fishtail <49390359+fuyuwei01@users.noreply.github.com> Date: Fri, 1 Aug 2025 16:06:41 +0800 Subject: [PATCH] feat:support fault injection. (#1691) Co-authored-by: Haotian Zhang --- CHANGELOG.md | 1 + spring-cloud-starter-tencent-all/pom.xml | 5 + .../PolarisCircuitBreakerTest.java | 2 + ...isCircuitBreakerAutoConfigurationTest.java | 2 + ...cuitBreakerBootstrapConfigurationTest.java | 2 + ...tBreakerEndpointAutoConfigurationTest.java | 2 + .../RouterBootstrapAutoConfigurationTest.java | 2 + .../common/constant/ContextConstant.java | 7 ++ .../cloud/common/constant/OrderConstant.java | 5 + spring-cloud-tencent-coverage/pom.xml | 6 + spring-cloud-tencent-dependencies/pom.xml | 6 + .../controller/ConsumerController.java | 18 ++- spring-cloud-tencent-plugin-starters/pom.xml | 1 + .../pom.xml | 29 +++++ .../plugin/fault/FaultInjectionPrePlugin.java | 118 ++++++++++++++++++ .../FaultInjectionAutoConfiguration.java | 48 +++++++ .../fault/config/FaultInjectionModifier.java | 53 ++++++++ .../config/FaultInjectionProperties.java | 49 ++++++++ .../PolarisFaultInjectionHttpResponse.java | 106 ++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../context/PolarisSDKContextManager.java | 17 +++ spring-cloud-tencent-rpc-enhancement/pom.xml | 12 ++ .../RpcEnhancementAutoConfiguration.java | 13 +- ...nhancementPropertiesAutoConfiguration.java | 42 +++++++ ...mentPropertiesBootstrapConfiguration.java} | 4 +- .../instrument/feign/EnhancedFeignClient.java | 28 +++++ ...BlockingLoadBalancerClientInterceptor.java | 34 +++++ .../scg/EnhancedGatewayGlobalFilter.java | 81 +++++++++--- ...hancedWebClientExchangeFilterFunction.java | 37 +++++- .../plugin/DefaultEnhancedPluginRunner.java | 5 +- .../plugin/PluginOrderConstant.java | 6 + .../main/resources/META-INF/spring.factories | 2 +- ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../RpcEnhancementAutoConfigurationTest.java | 1 + 34 files changed, 710 insertions(+), 36 deletions(-) create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/pom.xml create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/FaultInjectionPrePlugin.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/config/FaultInjectionAutoConfiguration.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/config/FaultInjectionModifier.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/config/FaultInjectionProperties.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/instrument/resttemplate/PolarisFaultInjectionHttpResponse.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementPropertiesAutoConfiguration.java rename spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/{RpcEnhancementBootstrapConfiguration.java => RpcEnhancementPropertiesBootstrapConfiguration.java} (90%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2f545ca6..b5a2e141c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,3 +17,4 @@ - [feat: support custom quickstart circuitbreak delay time.](https://github.com/Tencent/spring-cloud-tencent/pull/1688) - [feat: add delay interface in tsf-example.](https://github.com/Tencent/spring-cloud-tencent/pull/1689) - [fix: fix lb configuration on bootstrap step.](https://github.com/Tencent/spring-cloud-tencent/issues/1690) +- [feat:support fault injection.](https://github.com/Tencent/spring-cloud-tencent/pull/1691) diff --git a/spring-cloud-starter-tencent-all/pom.xml b/spring-cloud-starter-tencent-all/pom.xml index c5caacc5a..975f99bd9 100644 --- a/spring-cloud-starter-tencent-all/pom.xml +++ b/spring-cloud-starter-tencent-all/pom.xml @@ -79,5 +79,10 @@ com.tencent.cloud spring-cloud-starter-tencent-traffic-mirroring-plugin + + + com.tencent.cloud + spring-cloud-starter-tencent-fault-injection-plugin + diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerTest.java index a7cf3fa97..6e3f66704 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerTest.java @@ -28,6 +28,7 @@ import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAuto import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementPropertiesAutoConfiguration; import com.tencent.polaris.client.util.Utils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -57,6 +58,7 @@ public class PolarisCircuitBreakerTest { private static ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of( PolarisContextAutoConfiguration.class, + RpcEnhancementPropertiesAutoConfiguration.class, RpcEnhancementAutoConfiguration.class, LoadBalancerAutoConfiguration.class, PolarisCircuitBreakerFeignClientAutoConfiguration.class, diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfigurationTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfigurationTest.java index 456020e78..ac77c52d7 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfigurationTest.java @@ -20,6 +20,7 @@ package com.tencent.cloud.polaris.circuitbreaker.config; import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementPropertiesAutoConfiguration; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -39,6 +40,7 @@ public class PolarisCircuitBreakerAutoConfigurationTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of( PolarisContextAutoConfiguration.class, + RpcEnhancementPropertiesAutoConfiguration.class, RpcEnhancementAutoConfiguration.class, LoadBalancerAutoConfiguration.class, PolarisCircuitBreakerFeignClientAutoConfiguration.class, diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfigurationTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfigurationTest.java index 07e482ca7..41b7315f3 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfigurationTest.java @@ -20,6 +20,7 @@ package com.tencent.cloud.polaris.circuitbreaker.config; import com.tencent.cloud.polaris.context.PolarisSDKContextManager; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementPropertiesAutoConfiguration; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -38,6 +39,7 @@ public class PolarisCircuitBreakerBootstrapConfigurationTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of( PolarisContextAutoConfiguration.class, + RpcEnhancementPropertiesAutoConfiguration.class, RpcEnhancementAutoConfiguration.class, LoadBalancerAutoConfiguration.class, PolarisCircuitBreakerBootstrapConfiguration.class)) diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpointAutoConfigurationTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpointAutoConfigurationTest.java index 5e4042704..1937f6842 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpointAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/endpoint/PolarisCircuitBreakerEndpointAutoConfigurationTest.java @@ -20,6 +20,7 @@ package com.tencent.cloud.polaris.circuitbreaker.endpoint; import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementPropertiesAutoConfiguration; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -37,6 +38,7 @@ public class PolarisCircuitBreakerEndpointAutoConfigurationTest { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of( PolarisContextAutoConfiguration.class, + RpcEnhancementPropertiesAutoConfiguration.class, RpcEnhancementAutoConfiguration.class, PolarisCircuitBreakerAutoConfiguration.class, PolarisCircuitBreakerEndpointAutoConfiguration.class diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/RouterBootstrapAutoConfigurationTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/RouterBootstrapAutoConfigurationTest.java index 189d527ec..c49cd0de7 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/RouterBootstrapAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/RouterBootstrapAutoConfigurationTest.java @@ -21,6 +21,7 @@ import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; import com.tencent.cloud.polaris.router.RouterConfigModifier; import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementPropertiesAutoConfiguration; import com.tencent.polaris.api.config.Configuration; import com.tencent.polaris.api.config.consumer.ServiceRouterConfig; import com.tencent.polaris.factory.ConfigAPIFactory; @@ -44,6 +45,7 @@ public class RouterBootstrapAutoConfigurationTest { .withConfiguration(AutoConfigurations.of( PolarisContextAutoConfiguration.class, PolarisNearByRouterProperties.class, + RpcEnhancementPropertiesAutoConfiguration.class, RpcEnhancementAutoConfiguration.class, RouterBootstrapAutoConfiguration.class)) .withPropertyValues("spring.cloud.polaris.enabled=true") diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/ContextConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/ContextConstant.java index c9231298f..9a0056185 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/ContextConstant.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/ContextConstant.java @@ -81,4 +81,11 @@ public final class ContextConstant { } } + + public static final class FaultInjection { + /** + * fault injection fallback http response. + */ + public static final String FAULT_INJECTION_FALLBACK_HTTP_RESPONSE = "FAULT_INJECTION_FALLBACK_HTTP_RESPONSE"; + } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java index c6ff02c84..79c2eb027 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java @@ -216,5 +216,10 @@ public class OrderConstant { * Order of push gateway event reporter modifier. */ public static Integer PUSH_GATEWAY_EVENT_ORDER = 2; + + /** + * Order of fault injection modifier. + */ + public static Integer FAULT_INJECTION_ORDER = 2; } } diff --git a/spring-cloud-tencent-coverage/pom.xml b/spring-cloud-tencent-coverage/pom.xml index 8a086a055..689000c07 100644 --- a/spring-cloud-tencent-coverage/pom.xml +++ b/spring-cloud-tencent-coverage/pom.xml @@ -100,6 +100,12 @@ spring-cloud-starter-tencent-traffic-mirroring-plugin ${revision} + + + com.tencent.cloud + spring-cloud-starter-tencent-fault-injection-plugin + ${revision} + diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index 64ca4b6f0..053d14568 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -208,6 +208,12 @@ ${revision} + + com.tencent.cloud + spring-cloud-starter-tencent-fault-injection-plugin + ${revision} + + org.springdoc diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/ConsumerController.java b/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/ConsumerController.java index fb365be8a..9a2423956 100644 --- a/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/ConsumerController.java +++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/ConsumerController.java @@ -30,6 +30,11 @@ import com.tencent.polaris.api.utils.StringUtils; import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.tsf.core.TsfContext; import org.springframework.tsf.core.entity.Tag; import org.springframework.web.bind.annotation.PathVariable; @@ -39,6 +44,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestTemplate; @RestController @@ -51,7 +58,7 @@ public class ConsumerController { private ProviderDemoService providerDemoService; @RequestMapping(value = "/echo-rest/{str}", method = RequestMethod.GET) - public String restProvider(@PathVariable String str, + public ResponseEntity restProvider(@PathVariable String str, @RequestParam(required = false) String tagName, @RequestParam(required = false) String tagValue) { if (StringUtils.isNotBlank(tagName)) { @@ -63,10 +70,15 @@ public class ConsumerController { mTags.put("rest-trace-key2", "value2"); TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE); try { - return restTemplate.getForObject("http://provider-demo/echo/" + str, String.class); + HttpHeaders headers = new HttpHeaders(); + HttpEntity entity = new HttpEntity<>(headers); + return restTemplate.exchange("http://provider-demo/echo/" + str, HttpMethod.GET, entity, String.class); } catch (CallAbortedException callAbortedException) { - return callAbortedException.getMessage(); + return new ResponseEntity<>(callAbortedException.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } + catch (HttpClientErrorException | HttpServerErrorException httpClientErrorException) { + return new ResponseEntity<>(httpClientErrorException.getResponseBodyAsString(), httpClientErrorException.getStatusCode()); } } @RequestMapping(value = "/echo-rest/slow/{str}", method = RequestMethod.GET) diff --git a/spring-cloud-tencent-plugin-starters/pom.xml b/spring-cloud-tencent-plugin-starters/pom.xml index 9a54eb55f..28f294aa6 100644 --- a/spring-cloud-tencent-plugin-starters/pom.xml +++ b/spring-cloud-tencent-plugin-starters/pom.xml @@ -23,6 +23,7 @@ spring-cloud-starter-tencent-fault-tolerance spring-cloud-starter-tencent-multi-discovery-plugin spring-cloud-starter-tencent-traffic-mirroring-plugin + spring-cloud-starter-tencent-fault-injection-plugin diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/pom.xml b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/pom.xml new file mode 100644 index 000000000..f01e9ae74 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/pom.xml @@ -0,0 +1,29 @@ + + + + spring-cloud-tencent-plugin-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-starter-tencent-fault-injection-plugin + Spring Cloud Starter Tencent Fault Injection Plugin + + + + com.tencent.cloud + spring-cloud-tencent-rpc-enhancement + + + + org.springframework.boot + spring-boot-starter-web + true + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/FaultInjectionPrePlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/FaultInjectionPrePlugin.java new file mode 100644 index 000000000..a02b982bf --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/FaultInjectionPrePlugin.java @@ -0,0 +1,118 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.plugin.fault; + +import java.util.HashMap; + +import com.tencent.cloud.common.constant.ContextConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.plugin.fault.config.FaultInjectionProperties; +import com.tencent.cloud.plugin.fault.instrument.resttemplate.PolarisFaultInjectionHttpResponse; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; +import com.tencent.polaris.api.pojo.CircuitBreakerStatus; +import com.tencent.polaris.api.utils.ClassUtils; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.polaris.fault.api.core.FaultAPI; +import com.tencent.polaris.fault.api.rpc.AbortResult; +import com.tencent.polaris.fault.api.rpc.DelayResult; +import com.tencent.polaris.fault.api.rpc.FaultRequest; +import com.tencent.polaris.fault.api.rpc.FaultResponse; +import com.tencent.polaris.fault.client.exception.FaultInjectionException; +import com.tencent.polaris.metadata.core.MetadataType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.tencent.cloud.common.metadata.MetadataContext.LOCAL_NAMESPACE; +import static com.tencent.cloud.common.metadata.MetadataContext.LOCAL_SERVICE; +import static com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant.ClientPluginOrder.FAULT_INJECTION_PLUGIN_ORDER; + +/** + * Fault injection pre plugin. + * + * @author Haotian Zhang + */ +public class FaultInjectionPrePlugin implements EnhancedPlugin { + + private static final Logger LOG = LoggerFactory.getLogger(FaultInjectionPrePlugin.class); + + private final FaultAPI faultAPI; + + private final FaultInjectionProperties faultInjectionProperties; + + public FaultInjectionPrePlugin(FaultAPI faultAPI, FaultInjectionProperties faultInjectionProperties) { + this.faultAPI = faultAPI; + this.faultInjectionProperties = faultInjectionProperties; + } + + @Override + public String getName() { + return FaultInjectionPrePlugin.class.getName(); + } + + @Override + public EnhancedPluginType getType() { + return EnhancedPluginType.Client.PRE; + } + + @Override + public void run(EnhancedPluginContext context) throws Throwable { + if (!faultInjectionProperties.isEnabled()) { + return; + } + + EnhancedRequestContext request = context.getRequest(); + String governanceNamespace = StringUtils.isNotEmpty(request.getGovernanceNamespace()) ? request.getGovernanceNamespace() : MetadataContext.LOCAL_NAMESPACE; + FaultRequest faultRequest = new FaultRequest(LOCAL_NAMESPACE, LOCAL_SERVICE, governanceNamespace, request.getHost(), MetadataContextHolder.get()); + + FaultResponse faultResponse = faultAPI.fault(faultRequest); + if (faultResponse.isFaultInjected()) { + if (faultResponse.getDelayResult() != null && faultResponse.getDelayResult().getDelay() > 0) { + DelayResult delayResult = faultResponse.getDelayResult(); + Thread.sleep(delayResult.getDelay()); + } + if (faultResponse.getAbortResult() != null) { + AbortResult abortResult = faultResponse.getAbortResult(); + CircuitBreakerStatus.FallbackInfo fallbackInfo = new CircuitBreakerStatus.FallbackInfo(abortResult.getAbortCode(), new HashMap<>(), ""); + if (ClassUtils.isClassPresent("org.springframework.http.client.ClientHttpResponse")) { + Object fallbackResponse = new PolarisFaultInjectionHttpResponse(fallbackInfo); + putMetadataObjectValue(ContextConstant.FaultInjection.FAULT_INJECTION_FALLBACK_HTTP_RESPONSE, fallbackResponse); + } + throw new FaultInjectionException(fallbackInfo); + } + } + } + + private void putMetadataObjectValue(String key, Object value) { + MetadataContextHolder.get().getMetadataContainer(MetadataType.APPLICATION, true). + putMetadataObjectValue(key, value); + } + + @Override + public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) { + LOG.error("FaultInjectionPrePlugin runs failed. context=[{}].", context, throwable); + } + + @Override + public int getOrder() { + return FAULT_INJECTION_PLUGIN_ORDER; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/config/FaultInjectionAutoConfiguration.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/config/FaultInjectionAutoConfiguration.java new file mode 100644 index 000000000..32aedb416 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/config/FaultInjectionAutoConfiguration.java @@ -0,0 +1,48 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.plugin.fault.config; + +import com.tencent.cloud.plugin.fault.FaultInjectionPrePlugin; +import com.tencent.cloud.polaris.context.PolarisSDKContextManager; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Autoconfiguration for fault injection plugin. + * + * @author Haotian Zhang + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(value = "spring.cloud.polaris.fault-injection.enabled", matchIfMissing = true) +@EnableConfigurationProperties(FaultInjectionProperties.class) +public class FaultInjectionAutoConfiguration { + + @Bean + public FaultInjectionModifier faultInjectionModifier(FaultInjectionProperties faultInjectionProperties) { + return new FaultInjectionModifier(faultInjectionProperties); + } + + @Bean + public FaultInjectionPrePlugin faultInjectionPrePlugin(PolarisSDKContextManager polarisSDKContextManager, + FaultInjectionProperties faultInjectionProperties) { + return new FaultInjectionPrePlugin(polarisSDKContextManager.getFaultAPI(), faultInjectionProperties); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/config/FaultInjectionModifier.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/config/FaultInjectionModifier.java new file mode 100644 index 000000000..07f6fcaaa --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/config/FaultInjectionModifier.java @@ -0,0 +1,53 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.plugin.fault.config; + +import com.tencent.cloud.common.constant.OrderConstant; +import com.tencent.cloud.polaris.context.PolarisConfigModifier; +import com.tencent.polaris.factory.config.ConfigurationImpl; +import com.tencent.polaris.factory.config.consumer.FaultConfigImpl; + +/** + * Properties for fault injection. + * + * @author Haotian Zhang + */ +public class FaultInjectionModifier implements PolarisConfigModifier { + + private final FaultInjectionProperties faultInjectionProperties; + + public FaultInjectionModifier(FaultInjectionProperties faultInjectionProperties) { + this.faultInjectionProperties = faultInjectionProperties; + } + + @Override + public void modify(ConfigurationImpl configuration) { + FaultConfigImpl faultConfig = (FaultConfigImpl) configuration.getConsumer().getFault(); + if (faultInjectionProperties.isEnabled()) { + faultInjectionProperties.setEnabled(true); + } + else { + faultConfig.setEnable(false); + } + } + + @Override + public int getOrder() { + return OrderConstant.Modifier.FAULT_INJECTION_ORDER; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/config/FaultInjectionProperties.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/config/FaultInjectionProperties.java new file mode 100644 index 000000000..af2af1842 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/config/FaultInjectionProperties.java @@ -0,0 +1,49 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.plugin.fault.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Properties for fault injection. + * + * @author Haotian Zhang + */ +@ConfigurationProperties("spring.cloud.polaris.fault-injection") +public class FaultInjectionProperties { + + /** + * If traffic mirroring is enabled. Default is true. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public String toString() { + return "FaultInjectionProperties{" + + "enabled=" + enabled + + '}'; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/instrument/resttemplate/PolarisFaultInjectionHttpResponse.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/instrument/resttemplate/PolarisFaultInjectionHttpResponse.java new file mode 100644 index 000000000..c23f0ec13 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/java/com/tencent/cloud/plugin/fault/instrument/resttemplate/PolarisFaultInjectionHttpResponse.java @@ -0,0 +1,106 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.plugin.fault.instrument.resttemplate; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import com.tencent.polaris.api.pojo.CircuitBreakerStatus; +import org.jetbrains.annotations.NotNull; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.client.ClientHttpResponse; + +/** + * PolarisFaultInjectionHttpResponse. + * + * @author Haotian Zhang + */ +public class PolarisFaultInjectionHttpResponse implements ClientHttpResponse { + + private final CircuitBreakerStatus.FallbackInfo fallbackInfo; + + private HttpHeaders headers = new HttpHeaders(); + + private InputStream body; + + public PolarisFaultInjectionHttpResponse(int code) { + this(new CircuitBreakerStatus.FallbackInfo(code, null, null)); + } + + public PolarisFaultInjectionHttpResponse(int code, String body) { + this(new CircuitBreakerStatus.FallbackInfo(code, null, body)); + } + + public PolarisFaultInjectionHttpResponse(int code, Map headers, String body) { + this(new CircuitBreakerStatus.FallbackInfo(code, headers, body)); + } + + public PolarisFaultInjectionHttpResponse(CircuitBreakerStatus.FallbackInfo fallbackInfo) { + this.fallbackInfo = fallbackInfo; + if (fallbackInfo.getHeaders() != null) { + fallbackInfo.getHeaders().forEach(headers::add); + } + if (fallbackInfo.getBody() != null) { + body = new ByteArrayInputStream(fallbackInfo.getBody().getBytes()); + } + } + + @NotNull + @Override + public HttpStatusCode getStatusCode() { + return HttpStatus.valueOf(fallbackInfo.getCode()); + } + + @NotNull + @Override + public final String getStatusText() { + HttpStatus status = HttpStatus.resolve(getStatusCode().value()); + return (status != null ? status.getReasonPhrase() : ""); + } + + @Override + public final void close() { + if (this.body != null) { + try { + this.body.close(); + } + catch (IOException e) { + // Ignore exception on close... + } + } + } + + @Override + public final InputStream getBody() { + return this.body; + } + + @Override + public final HttpHeaders getHeaders() { + return this.headers; + } + + public CircuitBreakerStatus.FallbackInfo getFallbackInfo() { + return this.fallbackInfo; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..d79e06104 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-fault-injection-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.tencent.cloud.plugin.fault.config.FaultInjectionAutoConfiguration diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisSDKContextManager.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisSDKContextManager.java index 05ab1966f..25538ea70 100644 --- a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisSDKContextManager.java +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisSDKContextManager.java @@ -37,6 +37,8 @@ import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory; import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.factory.api.DiscoveryAPIFactory; import com.tencent.polaris.factory.api.RouterAPIFactory; +import com.tencent.polaris.fault.api.core.FaultAPI; +import com.tencent.polaris.fault.factory.FaultAPIFactory; import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.factory.LimitAPIFactory; import com.tencent.polaris.router.api.core.RouterAPI; @@ -67,6 +69,7 @@ public class PolarisSDKContextManager { private volatile static CircuitBreakAPI circuitBreakAPI; private volatile static LimitAPI limitAPI; private volatile static AuthAPI authAPI; + private volatile static FaultAPI faultAPI; private volatile static AssemblyAPI assemblyAPI; private final PolarisContextProperties properties; private final Environment environment; @@ -126,6 +129,12 @@ public class PolarisSDKContextManager { authAPI = null; } + // destroy FaultAPI + if (Objects.nonNull(faultAPI)) { + ((AutoCloseable) faultAPI).close(); + faultAPI = null; + } + // destroy AssemblyAPI if (Objects.nonNull(assemblyAPI)) { ((Destroyable) assemblyAPI).destroy(); @@ -212,6 +221,11 @@ public class PolarisSDKContextManager { return authAPI; } + public FaultAPI getFaultAPI() { + initService(); + return faultAPI; + } + public AssemblyAPI getAssemblyAPI() { return assemblyAPI; } @@ -271,6 +285,9 @@ public class PolarisSDKContextManager { // init AuthAPI authAPI = AuthAPIFactory.createAuthAPIByContext(serviceSdkContext); + // init FaultAPI + faultAPI = FaultAPIFactory.createFaultAPIByContext(serviceSdkContext); + // init AssemblyAPI assemblyAPI = AssemblyAPIFactory.createAssemblyAPIByContext(serviceSdkContext); diff --git a/spring-cloud-tencent-rpc-enhancement/pom.xml b/spring-cloud-tencent-rpc-enhancement/pom.xml index bc67386be..b7a766f90 100644 --- a/spring-cloud-tencent-rpc-enhancement/pom.xml +++ b/spring-cloud-tencent-rpc-enhancement/pom.xml @@ -8,6 +8,18 @@ ${revision} ../pom.xml + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + 4.0.0 spring-cloud-tencent-rpc-enhancement diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java index 82a54b4b7..6461cfba3 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java @@ -53,7 +53,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer; @@ -84,7 +83,6 @@ import static jakarta.servlet.DispatcherType.REQUEST; @Configuration(proxyBeanMethods = false) @ConditionalOnPolarisEnabled @ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true) -@EnableConfigurationProperties(RpcEnhancementReporterProperties.class) @AutoConfigureAfter(PolarisContextAutoConfiguration.class) public class RpcEnhancementAutoConfiguration { @@ -189,6 +187,9 @@ public class RpcEnhancementAutoConfiguration { @ConditionalOnClass(name = "org.springframework.web.client.RestTemplate") protected static class PolarisRestTemplateAutoConfiguration { + @Autowired(required = false) + private List restTemplates = Collections.emptyList(); + @Bean @ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor") public BlockingLoadBalancerClientBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() { @@ -202,14 +203,6 @@ public class RpcEnhancementAutoConfiguration { return new PolarisLoadBalancerRequestTransformer(); } - @Bean - @ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor") - public BlockingLoadBalancerClientBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() { - return new BlockingLoadBalancerClientBeanPostProcessor(); - } - @Autowired(required = false) - private List restTemplates = Collections.emptyList(); - @Bean public EnhancedRestTemplateInterceptor enhancedRestTemplateInterceptor(EnhancedPluginRunner pluginRunner) { return new EnhancedRestTemplateInterceptor(pluginRunner); diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementPropertiesAutoConfiguration.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementPropertiesAutoConfiguration.java new file mode 100644 index 000000000..1f33f81f8 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementPropertiesAutoConfiguration.java @@ -0,0 +1,42 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.rpc.enhancement.config; + +import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +/** + * Auto Configuration for Polaris {@link feign.Feign} OR {@link RestTemplate} which can automatically bring in the call + * results for reporting. + * + * @author Palmer.Xu 2022-06-29 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnPolarisEnabled +public class RpcEnhancementPropertiesAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public RpcEnhancementReporterProperties rpcEnhancementReporterProperties() { + return new RpcEnhancementReporterProperties(); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementBootstrapConfiguration.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementPropertiesBootstrapConfiguration.java similarity index 90% rename from spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementBootstrapConfiguration.java rename to spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementPropertiesBootstrapConfiguration.java index 647b094ea..822b69874 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementBootstrapConfiguration.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementPropertiesBootstrapConfiguration.java @@ -27,6 +27,6 @@ import org.springframework.context.annotation.Import; */ @Configuration(proxyBeanMethods = false) @ConditionalOnProperty("spring.cloud.polaris.enabled") -@Import(RpcEnhancementAutoConfiguration.class) -public class RpcEnhancementBootstrapConfiguration { +@Import(RpcEnhancementPropertiesAutoConfiguration.class) +public class RpcEnhancementPropertiesBootstrapConfiguration { } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/feign/EnhancedFeignClient.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/feign/EnhancedFeignClient.java index 407ddac3a..23fd882cb 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/feign/EnhancedFeignClient.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/feign/EnhancedFeignClient.java @@ -37,6 +37,7 @@ import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; import com.tencent.cloud.rpc.enhancement.util.EnhancedPluginUtils; import com.tencent.polaris.api.pojo.CircuitBreakerStatus; import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import com.tencent.polaris.fault.client.exception.FaultInjectionException; import feign.Client; import feign.Request; import feign.Request.Options; @@ -136,6 +137,33 @@ public class EnhancedFeignClient implements Client { throw callAbortedException; } } + catch (FaultInjectionException faultInjectionException) { + if (faultInjectionException.getFallbackInfo() != null) { + enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); + + Response response = getFallbackResponse(faultInjectionException.getFallbackInfo()); + + HttpHeaders responseHeaders = new HttpHeaders(); + response.headers().forEach((s, strings) -> responseHeaders.addAll(s, new ArrayList<>(strings))); + + EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder() + .httpStatus(response.status()) + .httpHeaders(responseHeaders) + .build(); + enhancedPluginContext.setResponse(enhancedResponseContext); + + // Run post enhanced plugins. + pluginRunner.run(EnhancedPluginType.Client.POST, enhancedPluginContext); + return response; + } + else { + enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); + enhancedPluginContext.setThrowable(faultInjectionException); + // Run exception enhanced feign plugins. + pluginRunner.run(EnhancedPluginType.Client.EXCEPTION, enhancedPluginContext); + throw faultInjectionException; + } + } catch (IOException origin) { enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); enhancedPluginContext.setThrowable(origin); diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateBlockingLoadBalancerClientInterceptor.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateBlockingLoadBalancerClientInterceptor.java index 19506542c..a34874541 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateBlockingLoadBalancerClientInterceptor.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateBlockingLoadBalancerClientInterceptor.java @@ -32,6 +32,7 @@ import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; import com.tencent.cloud.rpc.enhancement.util.EnhancedPluginUtils; import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import com.tencent.polaris.fault.client.exception.FaultInjectionException; import com.tencent.polaris.metadata.core.MetadataObjectValue; import com.tencent.polaris.metadata.core.MetadataType; @@ -140,6 +141,39 @@ public class EnhancedRestTemplateBlockingLoadBalancerClientInterceptor { } throw callAbortedException; } + catch (FaultInjectionException faultInjectionException) { + MetadataObjectValue fallbackResponseValue = MetadataContextHolder.get(). + getMetadataContainer(MetadataType.APPLICATION, true). + getMetadataValue(ContextConstant.FaultInjection.FAULT_INJECTION_FALLBACK_HTTP_RESPONSE); + + boolean existFallback = Optional.ofNullable(fallbackResponseValue). + map(MetadataObjectValue::getObjectValue).map(Optional::isPresent).orElse(false); + + enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); + if (existFallback) { + Object fallbackResponse = fallbackResponseValue.getObjectValue().orElse(null); + if (fallbackResponse instanceof ClientHttpResponse) { + ClientHttpResponse response = (ClientHttpResponse) fallbackResponse; + // get target instance after execute + enhancedPluginContext.setTargetServiceInstance((ServiceInstance) MetadataContextHolder.get() + .getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), httpRequest.getURI()); + + EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder() + .httpStatus(response.getStatusCode().value()) + .httpHeaders(response.getHeaders()) + .build(); + enhancedPluginContext.setResponse(enhancedResponseContext); + + // Run post enhanced plugins. + pluginRunner.run(EnhancedPluginType.Client.POST, enhancedPluginContext); + return (T) response; + } + } + enhancedPluginContext.setThrowable(faultInjectionException); + // Run exception enhanced plugins. + pluginRunner.run(EnhancedPluginType.Client.EXCEPTION, enhancedPluginContext); + throw faultInjectionException; + } catch (IOException e) { enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); enhancedPluginContext.setThrowable(e); diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilter.java index 20dbc8278..de47234f3 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilter.java @@ -34,6 +34,7 @@ import com.tencent.cloud.rpc.enhancement.util.EnhancedPluginUtils; import com.tencent.polaris.api.utils.CollectionUtils; import com.tencent.polaris.api.utils.StringUtils; import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import com.tencent.polaris.fault.client.exception.FaultInjectionException; import reactor.core.publisher.Mono; import org.springframework.cloud.client.DefaultServiceInstance; @@ -96,6 +97,7 @@ public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered { enhancedPluginContext.setRequest(enhancedRequestContext); enhancedPluginContext.setOriginRequest(originExchange); + long startTime = System.currentTimeMillis(); // Run pre enhanced plugins. try { pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext); @@ -117,27 +119,53 @@ public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered { DataBuffer dataBuffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(dataBuffer)); } + catch (FaultInjectionException faultInjectionException) { + enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime); + if (faultInjectionException.getFallbackInfo() != null) { + ServerHttpResponse response = originExchange.getResponse(); + HttpStatus httpStatus = HttpStatus.resolve(faultInjectionException.getFallbackInfo().getCode()); + response.setStatusCode(httpStatus != null ? httpStatus : HttpStatus.INTERNAL_SERVER_ERROR); + if (CollectionUtils.isNotEmpty(faultInjectionException.getFallbackInfo().getHeaders())) { + faultInjectionException.getFallbackInfo().getHeaders().forEach(response.getHeaders()::set); + } + String body = Optional.of(faultInjectionException.getFallbackInfo().getBody()).orElse(""); + DataBuffer dataBuffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); + + EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder() + .httpStatus(faultInjectionException.getFallbackInfo().getCode()) + .httpHeaders(response.getHeaders()) + .build(); + enhancedPluginContext.setResponse(enhancedResponseContext); + setTargetServiceInstance(originExchange, enhancedPluginContext, serviceId); + + // Run post enhanced plugins. + pluginRunner.run(EnhancedPluginType.Client.POST, enhancedPluginContext); + + pluginRunner.run(EnhancedPluginType.Client.FINALLY, enhancedPluginContext); + + return response.writeWith(Mono.just(dataBuffer)); + } + else { + MetadataContext metadataContextOnError = originExchange.getAttribute( + MetadataConstant.HeaderName.METADATA_CONTEXT); + if (metadataContextOnError != null) { + MetadataContextHolder.set(metadataContextOnError); + } + + enhancedPluginContext.setThrowable(faultInjectionException); + + // Run exception enhanced plugins. + pluginRunner.run(EnhancedPluginType.Client.EXCEPTION, enhancedPluginContext); + + pluginRunner.run(EnhancedPluginType.Client.FINALLY, enhancedPluginContext); + throw faultInjectionException; + } + } // Exchange may be changed in plugin ServerWebExchange exchange = (ServerWebExchange) enhancedPluginContext.getOriginRequest(); - long startTime = System.currentTimeMillis(); return chain.filter(exchange) .doOnSubscribe(v -> { - Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); - URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); - enhancedPluginContext.getRequest().setUrl(uri); - if (uri != null) { - if (route != null && route.getUri().getScheme() - .contains("lb") && StringUtils.isNotEmpty(serviceId)) { - DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); - serviceInstance.setServiceId(serviceId); - serviceInstance.setHost(uri.getHost()); - serviceInstance.setPort(uri.getPort()); - enhancedPluginContext.setTargetServiceInstance(serviceInstance, null); - } - else { - enhancedPluginContext.setTargetServiceInstance(null, uri); - } - } + setTargetServiceInstance(exchange, enhancedPluginContext, serviceId); pluginRunner.run(EnhancedPluginType.Client.BEFORE_CALLING, enhancedPluginContext); }) .doOnSuccess(v -> { @@ -187,6 +215,25 @@ public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered { return OrderConstant.Client.Scg.ENHANCED_FILTER_ORDER; } + private void setTargetServiceInstance(ServerWebExchange exchange, EnhancedPluginContext enhancedPluginContext, String serviceId) { + Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); + URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); + enhancedPluginContext.getRequest().setUrl(uri); + if (uri != null) { + if (route != null && route.getUri().getScheme() + .contains("lb") && StringUtils.isNotEmpty(serviceId)) { + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); + serviceInstance.setServiceId(serviceId); + serviceInstance.setHost(uri.getHost()); + serviceInstance.setPort(uri.getPort()); + enhancedPluginContext.setTargetServiceInstance(serviceInstance, null); + } + else { + enhancedPluginContext.setTargetServiceInstance(null, uri); + } + } + } + private URI getServiceUri(ServerWebExchange originExchange, String serviceId) { URI uri = originExchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); if (StringUtils.isEmpty(serviceId) || uri == null) { diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunction.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunction.java index d46403981..47e04388b 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunction.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunction.java @@ -32,6 +32,7 @@ import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; import com.tencent.cloud.rpc.enhancement.util.EnhancedPluginUtils; import com.tencent.polaris.api.utils.CollectionUtils; import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import com.tencent.polaris.fault.client.exception.FaultInjectionException; import reactor.core.publisher.Mono; import org.springframework.cloud.client.ServiceInstance; @@ -76,6 +77,7 @@ public class EnhancedWebClientExchangeFilterFunction implements ExchangeFilterFu enhancedPluginContext.setTargetServiceInstance((ServiceInstance) MetadataContextHolder.get() .getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), originRequest.url()); + long startTime = System.currentTimeMillis(); // Run post enhanced plugins. try { pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext); @@ -96,9 +98,42 @@ public class EnhancedWebClientExchangeFilterFunction implements ExchangeFilterFu } return Mono.just(responseBuilder.build()); } + catch (FaultInjectionException faultInjectionException) { + enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime); + if (faultInjectionException.getFallbackInfo() != null) { + HttpStatus httpStatus = HttpStatus.resolve(faultInjectionException.getFallbackInfo().getCode()); + ClientResponse.Builder responseBuilder = ClientResponse.create(httpStatus != null ? httpStatus : HttpStatus.INTERNAL_SERVER_ERROR) + .body(Optional.of(faultInjectionException.getFallbackInfo().getBody()).orElse("")); + if (CollectionUtils.isNotEmpty(faultInjectionException.getFallbackInfo().getHeaders())) { + faultInjectionException.getFallbackInfo().getHeaders().forEach(responseBuilder::header); + } + ClientResponse response = responseBuilder.build(); + + EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder() + .httpStatus(response.statusCode().value()) + .httpHeaders(response.headers().asHttpHeaders()) + .build(); + enhancedPluginContext.setResponse(enhancedResponseContext); + + // Run post enhanced plugins. + pluginRunner.run(EnhancedPluginType.Client.POST, enhancedPluginContext); + + pluginRunner.run(EnhancedPluginType.Client.FINALLY, enhancedPluginContext); + + return Mono.just(response); + } + else { + enhancedPluginContext.setThrowable(faultInjectionException); + + // Run exception enhanced plugins. + pluginRunner.run(EnhancedPluginType.Client.EXCEPTION, enhancedPluginContext); + + pluginRunner.run(EnhancedPluginType.Client.FINALLY, enhancedPluginContext); + throw faultInjectionException; + } + } // request may be changed by plugin ClientRequest request = (ClientRequest) enhancedPluginContext.getOriginRequest(); - long startTime = System.currentTimeMillis(); return next.exchange(request) .doOnSuccess(response -> { enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime); diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunner.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunner.java index 28c15edac..090473ace 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunner.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunner.java @@ -23,6 +23,7 @@ import java.util.List; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.fault.client.exception.FaultInjectionException; import shade.polaris.com.google.common.collect.ArrayListMultimap; import shade.polaris.com.google.common.collect.Multimap; @@ -98,8 +99,8 @@ public class DefaultEnhancedPluginRunner implements EnhancedPluginRunner { try { plugin.run(context); } - catch (CallAbortedException callAbortedException) { - throw callAbortedException; + catch (CallAbortedException | FaultInjectionException rethrowException) { + throw rethrowException; } catch (Throwable throwable) { plugin.handlerThrowable(context, throwable); diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PluginOrderConstant.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PluginOrderConstant.java index cdce302f8..fae0be2b0 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PluginOrderConstant.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PluginOrderConstant.java @@ -52,6 +52,12 @@ public class PluginOrderConstant { */ public static final int TRAFFIC_MIRRORING_PLUGIN_ORDER = Ordered.HIGHEST_PRECEDENCE + 20; + /** + * order for + * {@link com.tencent.cloud.plugin.fault.FaultInjectionPrePlugin}. + */ + public static final int FAULT_INJECTION_PLUGIN_ORDER = Ordered.HIGHEST_PRECEDENCE + 40; + /** * order for * {@link com.tencent.cloud.metadata.core.EncodeTransferMedataFeignEnhancedPlugin} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/spring.factories index db9f51f95..c22541635 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/spring.factories @@ -1,3 +1,3 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatPropertiesBootstrapConfiguration,\ - com.tencent.cloud.rpc.enhancement.config.RpcEnhancementBootstrapConfiguration + com.tencent.cloud.rpc.enhancement.config.RpcEnhancementPropertiesBootstrapConfiguration diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 76c2a3cc0..c400229e0 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,2 +1,3 @@ com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatPropertiesAutoConfiguration +com.tencent.cloud.rpc.enhancement.config.RpcEnhancementPropertiesAutoConfiguration diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfigurationTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfigurationTest.java index 671e34f87..be2a6ad18 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfigurationTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfigurationTest.java @@ -45,6 +45,7 @@ public class RpcEnhancementAutoConfigurationTest { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of( PolarisContextAutoConfiguration.class, + RpcEnhancementPropertiesAutoConfiguration.class, RpcEnhancementAutoConfiguration.class, PolarisRestTemplateAutoConfigurationTester.class, FeignLoadBalancerAutoConfiguration.class))