feat:support fault injection. (#1707)

pull/1708/head
Haotian Zhang 3 weeks ago committed by GitHub
parent cccc20281c
commit 81ac3a54d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -15,3 +15,4 @@
- [feat: support custom quickstart circuitbreak delay time.](https://github.com/Tencent/spring-cloud-tencent/pull/1704)
- [feat: add delay interface in tsf-example.](https://github.com/Tencent/spring-cloud-tencent/pull/1705)
- [fix: fix lb configuration on bootstrap step.](https://github.com/Tencent/spring-cloud-tencent/issues/1706)
- [feat:support fault injection.](https://github.com/Tencent/spring-cloud-tencent/pull/1707)

@ -79,5 +79,10 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-traffic-mirroring-plugin</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-fault-injection-plugin</artifactId>
</dependency>
</dependencies>
</project>

@ -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,

@ -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,

@ -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))

@ -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

@ -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")

@ -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";
}
}

@ -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;
}
}

@ -100,6 +100,12 @@
<artifactId>spring-cloud-starter-tencent-traffic-mirroring-plugin</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-fault-injection-plugin</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>

@ -228,6 +228,12 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-fault-injection-plugin</artifactId>
<version>${revision}</version>
</dependency>
<!-- third part framework dependencies -->
<dependency>
<groupId>org.springdoc</groupId>

@ -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<String> 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<String> 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());
}
}

@ -24,6 +24,7 @@
<module>spring-cloud-tencent-security-protection-plugin</module>
<module>spring-cloud-starter-tencent-multi-discovery-plugin</module>
<module>spring-cloud-starter-tencent-traffic-mirroring-plugin</module>
<module>spring-cloud-starter-tencent-fault-injection-plugin</module>
</modules>
</project>

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-starter-tencent-fault-injection-plugin</artifactId>
<name>Spring Cloud Starter Tencent Fault Injection Plugin</name>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

@ -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;
}
}

@ -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);
}
}

@ -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;
}
}

@ -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 +
'}';
}
}

@ -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<String, String> 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;
}
}

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.plugin.fault.config.FaultInjectionAutoConfiguration

@ -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);

@ -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 javax.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 {
@ -205,12 +203,6 @@ public class RpcEnhancementAutoConfiguration {
return new PolarisLoadBalancerRequestTransformer();
}
@Bean
@ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor")
public BlockingLoadBalancerClientBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() {
return new BlockingLoadBalancerClientBeanPostProcessor();
}
@Bean
public EnhancedRestTemplateInterceptor enhancedRestTemplateInterceptor(EnhancedPluginRunner pluginRunner) {
return new EnhancedRestTemplateInterceptor(pluginRunner);

@ -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 <a href="mailto:iskp.me@gmail.com">Palmer.Xu</a> 2022-06-29
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled
public class RpcEnhancementPropertiesAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RpcEnhancementReporterProperties rpcEnhancementReporterProperties() {
return new RpcEnhancementReporterProperties();
}
}

@ -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 {
}

@ -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);

@ -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<Object> 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);

@ -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) {

@ -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);

@ -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);

@ -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}

@ -1,6 +1,9 @@
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.RpcEnhancementBootstrapConfiguration,\
com.tencent.cloud.rpc.enhancement.config.RpcEnhancementPropertiesBootstrapConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration,\
com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatPropertiesAutoConfiguration
com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatPropertiesAutoConfiguration,\
com.tencent.cloud.rpc.enhancement.config.RpcEnhancementPropertiesAutoConfiguration

@ -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))

Loading…
Cancel
Save