From bcc7b6635f39208b283d12f12033dac15764cf68 Mon Sep 17 00:00:00 2001 From: liaochuntao Date: Tue, 4 Apr 2023 11:19:05 +0800 Subject: [PATCH] feat:support webclient and gateway report call metrics (#924) Co-authored-by: Haotian Zhang <928016560@qq.com> --- CHANGELOG.md | 1 + .../circuitbreaker/PolarisCircuitBreaker.java | 13 +- .../PolarisCircuitBreakerFactory.java | 8 +- .../ReactivePolarisCircuitBreaker.java | 27 ++- .../ReactivePolarisCircuitBreakerFactory.java | 8 +- ...olarisCircuitBreakerAutoConfiguration.java | 5 +- ...olarisCircuitBreakerAutoConfiguration.java | 5 +- .../util/PolarisCircuitBreakerUtils.java | 39 +++- .../PolarisCircuitBreakerIntegrationTest.java | 5 +- .../PolarisCircuitBreakerMockServerTest.java | 7 +- .../util/PolarisCircuitBreakerUtilsTests.java | 71 +++++++ ...olarisLoadBalancerClientConfiguration.java | 12 ++ ...sLoadBalancerClientRequestTransformer.java | 44 +++++ .../filter/QuotaCheckReactiveFilter.java | 6 + .../filter/QuotaCheckServletFilter.java | 6 + .../filter/QuotaCheckReactiveFilterTest.java | 4 +- .../filter/QuotaCheckServletFilterTest.java | 4 +- .../cloud/common/constant/HeaderConstant.java | 52 ++++++ .../cloud/common/util/RequestLabelUtils.java | 34 ++++ .../polaris-ratelimit-example/README-zh.md | 6 +- .../polaris-ratelimit-example/README.md | 9 +- .../polaris-ratelimit-example/pom.xml | 1 + .../service/callee/BusinessController.java | 1 + .../ratelimit-caller-service/pom.xml | 51 ++++++ .../example/service/caller/Controller.java | 171 +++++++++++++++++ .../caller/RateLimitCallerService.java | 52 ++++++ .../src/main/resources/bootstrap.yml | 20 ++ spring-cloud-tencent-rpc-enhancement/pom.xml | 12 ++ .../AbstractPolarisReporterAdapter.java | 32 ++++ .../RpcEnhancementAutoConfiguration.java | 60 +++++- .../reporter/ExceptionPolarisReporter.java | 20 +- .../feign/plugin/reporter/ReporterUtils.java | 19 +- .../reporter/SuccessPolarisReporter.java | 17 +- .../EnhancedRestTemplateReporter.java | 21 ++- .../scg/EnhancedPolarisHttpClient.java | 173 ++++++++++++++++++ .../EnhancedPolarisHttpClientCustomizer.java | 43 +++++ .../scg/EnhancedPolarisHttpHeadersFilter.java | 71 +++++++ .../webclient/EnhancedWebClientReporter.java | 144 +++++++++++++++ .../AbstractPolarisReporterAdapterTest.java | 45 +++++ .../RpcEnhancementAutoConfigurationTest.java | 2 +- .../RpcEnhancementReporterPropertiesTest.java | 5 +- .../feign/EnhancedFeignClientTest.java | 2 +- .../ExceptionPolarisReporterTest.java | 53 +++++- .../plugin/reporter/ReporterUtilsTest.java | 21 ++- .../reporter/SuccessPolarisReporterTest.java | 52 +++++- ...hancedPolarisHttpClientCustomizerTest.java | 49 +++++ .../EnhancedPolarisHttpHeadersFilterTest.java | 57 ++++++ .../config/PolarisStatPropertiesTest.java | 3 +- .../stat/config/StatConfigModifierTest.java | 9 +- .../EnhancedWebClientReporterTest.java | 100 ++++++++++ 50 files changed, 1607 insertions(+), 65 deletions(-) create mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtilsTests.java create mode 100644 spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/reactive/PolarisLoadBalancerClientRequestTransformer.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/HeaderConstant.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/RequestLabelUtils.java create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/src/main/java/com/tencent/cloud/ratelimit/example/service/caller/Controller.java create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/src/main/java/com/tencent/cloud/ratelimit/example/service/caller/RateLimitCallerService.java create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClient.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizer.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilter.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporter.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizerTest.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilterTest.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporterTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index d6d77de26..30e12b89c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Change Log --- +- [feat:support webclient and gateway report call metrics](https://github.com/Tencent/spring-cloud-tencent/pull/924) - [feature: optimize polaris-discovery-example/discovery-callee-service, add client-ip return.](https://github.com/Tencent/spring-cloud-tencent/pull/939) - [docs:prevent the release of the final version of the sdk.](https://github.com/Tencent/spring-cloud-tencent/pull/943) diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreaker.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreaker.java index d1d70b9e3..3a6914940 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreaker.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreaker.java @@ -22,6 +22,8 @@ import java.util.function.Supplier; import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder; import com.tencent.cloud.polaris.circuitbreaker.common.PolarisResultToErrorCode; +import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils; +import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import com.tencent.polaris.circuitbreak.api.FunctionalDecorator; @@ -43,10 +45,18 @@ public class PolarisCircuitBreaker implements CircuitBreaker { private final FunctionalDecorator decorator; - public PolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, CircuitBreakAPI circuitBreakAPI) { + private final PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf; + + private final ConsumerAPI consumerAPI; + + public PolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, + ConsumerAPI consumerAPI, + CircuitBreakAPI circuitBreakAPI) { FunctionalDecoratorRequest makeDecoratorRequest = new FunctionalDecoratorRequest(new ServiceKey(conf.getNamespace(), conf.getService()), conf.getMethod()); makeDecoratorRequest.setSourceService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService())); makeDecoratorRequest.setResultToErrorCode(new PolarisResultToErrorCode()); + this.consumerAPI = consumerAPI; + this.conf = conf; this.decorator = circuitBreakAPI.makeFunctionalDecorator(makeDecoratorRequest); } @@ -58,6 +68,7 @@ public class PolarisCircuitBreaker implements CircuitBreaker { } catch (CallAbortedException e) { LOGGER.debug("PolarisCircuitBreaker CallAbortedException: {}", e.getMessage()); + PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, e); return fallback.apply(e); } catch (Exception e) { diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerFactory.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerFactory.java index d80ae90e5..1c789befe 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerFactory.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerFactory.java @@ -21,6 +21,7 @@ import java.util.function.Function; import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder; import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils; +import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; @@ -47,15 +48,18 @@ public class PolarisCircuitBreakerFactory private final CircuitBreakAPI circuitBreakAPI; - public PolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI) { + private final ConsumerAPI consumerAPI; + + public PolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) { this.circuitBreakAPI = circuitBreakAPI; + this.consumerAPI = consumerAPI; } @Override public CircuitBreaker create(String id) { PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf = getConfigurations() .computeIfAbsent(id, defaultConfiguration); - return new PolarisCircuitBreaker(conf, circuitBreakAPI); + return new PolarisCircuitBreaker(conf, consumerAPI, circuitBreakAPI); } @Override diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreaker.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreaker.java index cfd7e481a..56b633ef4 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreaker.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreaker.java @@ -22,11 +22,14 @@ import java.util.function.Function; import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder; import com.tencent.cloud.polaris.circuitbreaker.common.PolarisResultToErrorCode; import com.tencent.cloud.polaris.circuitbreaker.reactor.PolarisCircuitBreakerReactorTransformer; +import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils; +import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import com.tencent.polaris.circuitbreak.api.InvokeHandler; import com.tencent.polaris.circuitbreak.api.pojo.FunctionalDecoratorRequest; import com.tencent.polaris.circuitbreak.api.pojo.InvokeContext; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -41,10 +44,18 @@ public class ReactivePolarisCircuitBreaker implements ReactiveCircuitBreaker { private final InvokeHandler invokeHandler; - public ReactivePolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, CircuitBreakAPI circuitBreakAPI) { + private final ConsumerAPI consumerAPI; + + private final PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf; + + public ReactivePolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, + ConsumerAPI consumerAPI, + CircuitBreakAPI circuitBreakAPI) { InvokeContext.RequestContext requestContext = new FunctionalDecoratorRequest(new ServiceKey(conf.getNamespace(), conf.getService()), conf.getMethod()); requestContext.setSourceService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService())); requestContext.setResultToErrorCode(new PolarisResultToErrorCode()); + this.consumerAPI = consumerAPI; + this.conf = conf; this.invokeHandler = circuitBreakAPI.makeInvokeHandler(requestContext); } @@ -53,7 +64,12 @@ public class ReactivePolarisCircuitBreaker implements ReactiveCircuitBreaker { public Mono run(Mono toRun, Function> fallback) { Mono toReturn = toRun.transform(new PolarisCircuitBreakerReactorTransformer<>(invokeHandler)); if (fallback != null) { - toReturn = toReturn.onErrorResume(fallback); + toReturn = toReturn.onErrorResume(throwable -> { + if (throwable instanceof CallAbortedException) { + PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, (CallAbortedException) throwable); + } + return fallback.apply(throwable); + }); } return toReturn; } @@ -62,7 +78,12 @@ public class ReactivePolarisCircuitBreaker implements ReactiveCircuitBreaker { public Flux run(Flux toRun, Function> fallback) { Flux toReturn = toRun.transform(new PolarisCircuitBreakerReactorTransformer<>(invokeHandler)); if (fallback != null) { - toReturn = toReturn.onErrorResume(fallback); + toReturn = toReturn.onErrorResume(throwable -> { + if (throwable instanceof CallAbortedException) { + PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, (CallAbortedException) throwable); + } + return fallback.apply(throwable); + }); } return toReturn; } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreakerFactory.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreakerFactory.java index 4a481e866..e1ba4d2b4 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreakerFactory.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreakerFactory.java @@ -21,6 +21,7 @@ import java.util.function.Function; import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder; import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils; +import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker; @@ -46,8 +47,11 @@ public class ReactivePolarisCircuitBreakerFactory extends private final CircuitBreakAPI circuitBreakAPI; - public ReactivePolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI) { + private final ConsumerAPI consumerAPI; + + public ReactivePolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) { this.circuitBreakAPI = circuitBreakAPI; + this.consumerAPI = consumerAPI; } @@ -55,7 +59,7 @@ public class ReactivePolarisCircuitBreakerFactory extends public ReactiveCircuitBreaker create(String id) { PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf = getConfigurations() .computeIfAbsent(id, defaultConfiguration); - return new ReactivePolarisCircuitBreaker(conf, circuitBreakAPI); + return new ReactivePolarisCircuitBreaker(conf, consumerAPI, circuitBreakAPI); } @Override diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java index b2792737a..b73303f78 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java @@ -25,6 +25,7 @@ import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModif import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerRestTemplateBeanPostProcessor; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory; import com.tencent.polaris.client.api.SDKContext; @@ -62,8 +63,8 @@ public class PolarisCircuitBreakerAutoConfiguration { @Bean @ConditionalOnMissingBean(CircuitBreakerFactory.class) - public CircuitBreakerFactory polarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI) { - PolarisCircuitBreakerFactory factory = new PolarisCircuitBreakerFactory(circuitBreakAPI); + public CircuitBreakerFactory polarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) { + PolarisCircuitBreakerFactory factory = new PolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI); customizers.forEach(customizer -> customizer.customize(factory)); return factory; } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/ReactivePolarisCircuitBreakerAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/ReactivePolarisCircuitBreakerAutoConfiguration.java index 9d9bf911e..fdb89b2c9 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/ReactivePolarisCircuitBreakerAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/ReactivePolarisCircuitBreakerAutoConfiguration.java @@ -24,6 +24,7 @@ import com.tencent.cloud.polaris.circuitbreaker.ReactivePolarisCircuitBreakerFac import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory; import com.tencent.polaris.client.api.SDKContext; @@ -60,8 +61,8 @@ public class ReactivePolarisCircuitBreakerAutoConfiguration { @Bean @ConditionalOnMissingBean(ReactiveCircuitBreakerFactory.class) - public ReactiveCircuitBreakerFactory polarisReactiveCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI) { - ReactivePolarisCircuitBreakerFactory factory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI); + public ReactiveCircuitBreakerFactory polarisReactiveCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) { + ReactivePolarisCircuitBreakerFactory factory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI); customizers.forEach(customizer -> customizer.customize(factory)); return factory; } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtils.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtils.java index 885e2917b..4c7502c92 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtils.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtils.java @@ -17,7 +17,17 @@ package com.tencent.cloud.polaris.circuitbreaker.util; +import java.util.Objects; + import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.pojo.RetStatus; +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.util.Assert; @@ -28,6 +38,8 @@ import org.springframework.util.Assert; */ public final class PolarisCircuitBreakerUtils { + private static final Logger LOG = LoggerFactory.getLogger(PolarisCircuitBreakerUtils.class); + private PolarisCircuitBreakerUtils() { } @@ -43,12 +55,33 @@ public final class PolarisCircuitBreakerUtils { Assert.hasText(id, "A CircuitBreaker must have an id. Id could be : namespace#service#method or service#method or service"); String[] polarisCircuitBreakerMetaData = id.split("#"); if (polarisCircuitBreakerMetaData.length == 2) { - return new String[]{MetadataContext.LOCAL_NAMESPACE, polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1]}; + return new String[] {MetadataContext.LOCAL_NAMESPACE, polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1]}; } if (polarisCircuitBreakerMetaData.length == 3) { - return new String[]{polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1], polarisCircuitBreakerMetaData[2]}; + return new String[] {polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1], polarisCircuitBreakerMetaData[2]}; + } + return new String[] {MetadataContext.LOCAL_NAMESPACE, id, ""}; + } + + public static void reportStatus(ConsumerAPI consumerAPI, + PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, CallAbortedException e) { + try { + ServiceCallResult result = new ServiceCallResult(); + result.setMethod(conf.getMethod()); + result.setNamespace(conf.getNamespace()); + result.setService(conf.getService()); + result.setRuleName(e.getRuleName()); + result.setRetStatus(RetStatus.RetReject); + result.setCallerService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService())); + + if (Objects.nonNull(e.getFallbackInfo())) { + result.setRetCode(e.getFallbackInfo().getCode()); + } + consumerAPI.updateServiceCallResult(result); + } + catch (Throwable ex) { + LOG.error("[CircuitBreaker] report circuitbreaker call result fail ", ex); } - return new String[]{MetadataContext.LOCAL_NAMESPACE, id, ""}; } } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerIntegrationTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerIntegrationTest.java index a78b59aff..f4bd9ede1 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerIntegrationTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerIntegrationTest.java @@ -39,6 +39,7 @@ import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory; +import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.client.util.Utils; import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto; import com.tencent.polaris.test.common.TestUtils; @@ -175,9 +176,9 @@ public class PolarisCircuitBreakerIntegrationTest { @Bean @PolarisCircuitBreaker(fallback = "fallback") - public RestTemplate defaultRestTemplate(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { + public RestTemplate defaultRestTemplate(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { RestTemplate defaultRestTemplate = new RestTemplate(); - EnhancedRestTemplateReporter enhancedRestTemplateReporter = new EnhancedRestTemplateReporter(properties, consumerAPI); + EnhancedRestTemplateReporter enhancedRestTemplateReporter = new EnhancedRestTemplateReporter(properties, context, consumerAPI); defaultRestTemplate.setErrorHandler(enhancedRestTemplateReporter); return defaultRestTemplate; } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerMockServerTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerMockServerTest.java index 36976da74..89ab8154e 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerMockServerTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerMockServerTest.java @@ -31,10 +31,12 @@ import java.util.stream.Collectors; import com.google.protobuf.util.JsonFormat; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.polaris.api.config.Configuration; +import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory; import com.tencent.polaris.client.util.Utils; +import com.tencent.polaris.factory.api.DiscoveryAPIFactory; import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto; import com.tencent.polaris.test.common.TestUtils; import com.tencent.polaris.test.mock.discovery.NamingServer; @@ -104,8 +106,9 @@ public class PolarisCircuitBreakerMockServerTest { public void testCircuitBreaker() { Configuration configuration = TestUtils.configWithEnvAddress(); CircuitBreakAPI circuitBreakAPI = CircuitBreakAPIFactory.createCircuitBreakAPIByConfig(configuration); + ConsumerAPI consumerAPI = DiscoveryAPIFactory.createConsumerAPIByConfig(configuration); - PolarisCircuitBreakerFactory polarisCircuitBreakerFactory = new PolarisCircuitBreakerFactory(circuitBreakAPI); + PolarisCircuitBreakerFactory polarisCircuitBreakerFactory = new PolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI); CircuitBreaker cb = polarisCircuitBreakerFactory.create(SERVICE_CIRCUIT_BREAKER); // trigger fallback for 5 times @@ -126,7 +129,7 @@ public class PolarisCircuitBreakerMockServerTest { assertThat(resList).isEqualTo(Arrays.asList("invoke success", "fallback", "fallback", "fallback", "fallback")); // always fallback - ReactivePolarisCircuitBreakerFactory reactivePolarisCircuitBreakerFactory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI); + ReactivePolarisCircuitBreakerFactory reactivePolarisCircuitBreakerFactory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI); ReactiveCircuitBreaker rcb = reactivePolarisCircuitBreakerFactory.create(SERVICE_CIRCUIT_BREAKER); assertThat(Mono.just("foobar").transform(it -> rcb.run(it, t -> Mono.just("fallback"))) diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtilsTests.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtilsTests.java new file mode 100644 index 000000000..e6213dbab --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtilsTests.java @@ -0,0 +1,71 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.circuitbreaker.util; + +import java.util.HashMap; + +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.pojo.CircuitBreakerStatus; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.mockito.ArgumentMatchers.anyString; + +public class PolarisCircuitBreakerUtilsTests { + + private static MockedStatic mockedApplicationContextAwareUtils; + + + @BeforeAll + static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); + } + + @AfterAll + static void afterAll() { + mockedApplicationContextAwareUtils.close(); + } + + @BeforeEach + void setUp() { + MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST; + MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER; + } + + @Test + public void testReportStatus() { + ConsumerAPI consumerAPI = Mockito.mock(ConsumerAPI.class); + Mockito.doNothing().when(consumerAPI).updateServiceCallResult(new ServiceCallResult()); + PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf = new PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration(); + PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, new CallAbortedException("mock", new CircuitBreakerStatus.FallbackInfo(0, new HashMap<>(), ""))); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerClientConfiguration.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerClientConfiguration.java index 3d9bf1a40..cce8b0a9f 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerClientConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerClientConfiguration.java @@ -18,6 +18,10 @@ package com.tencent.cloud.polaris.loadbalancer; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; +import com.tencent.cloud.polaris.loadbalancer.reactive.PolarisLoadBalancerClientRequestTransformer; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.factory.api.DiscoveryAPIFactory; import com.tencent.polaris.router.api.core.RouterAPI; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -29,6 +33,7 @@ import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerClientRequestTransformer; import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; @@ -65,6 +70,13 @@ public class PolarisLoadBalancerClientConfiguration { loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), routerAPI); } + @Bean + @ConditionalOnMissingBean + public LoadBalancerClientRequestTransformer polarisLoadBalancerClientRequestTransformer(SDKContext sdkContext) { + ConsumerAPI consumerAPI = DiscoveryAPIFactory.createConsumerAPIByContext(sdkContext); + return new PolarisLoadBalancerClientRequestTransformer(consumerAPI); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnReactiveDiscoveryEnabled @Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER) diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/reactive/PolarisLoadBalancerClientRequestTransformer.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/reactive/PolarisLoadBalancerClientRequestTransformer.java new file mode 100644 index 000000000..cee59298b --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/reactive/PolarisLoadBalancerClientRequestTransformer.java @@ -0,0 +1,44 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.loadbalancer.reactive; + +import com.tencent.cloud.common.constant.HeaderConstant; +import com.tencent.polaris.api.core.ConsumerAPI; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerClientRequestTransformer; +import org.springframework.http.HttpHeaders; +import org.springframework.web.reactive.function.client.ClientRequest; + +public class PolarisLoadBalancerClientRequestTransformer implements LoadBalancerClientRequestTransformer { + + private final ConsumerAPI consumerAPI; + + public PolarisLoadBalancerClientRequestTransformer(ConsumerAPI consumerAPI) { + this.consumerAPI = consumerAPI; + } + + @Override + public ClientRequest transformRequest(ClientRequest request, ServiceInstance instance) { + if (instance != null) { + HttpHeaders headers = request.headers(); + headers.add(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, instance.getServiceId()); + } + return request; + } +} diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java index db0075a90..11dd12fee 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java @@ -25,6 +25,7 @@ import java.util.Set; import javax.annotation.PostConstruct; +import com.tencent.cloud.common.constant.HeaderConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; @@ -32,6 +33,7 @@ import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentReactiv import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils; import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils; +import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.rpc.Argument; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; @@ -116,6 +118,10 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { dataBuffer = response.bufferFactory().allocateBuffer() .write(rejectTips.getBytes(StandardCharsets.UTF_8)); } + response.getHeaders().add(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc()); + if (Objects.nonNull(quotaResponse.getActiveRule())) { + response.getHeaders().add(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, quotaResponse.getActiveRule().getName().getValue()); + } return response.writeWith(Mono.just(dataBuffer)); } // Unirate diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java index 1bc3a5203..027ae91f2 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java @@ -28,6 +28,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import com.tencent.cloud.common.constant.HeaderConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; @@ -35,6 +36,7 @@ import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServlet import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils; import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils; +import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.rpc.Argument; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; @@ -111,6 +113,10 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter { response.setContentType("text/html;charset=UTF-8"); response.getWriter().write(rejectTips); } + response.addHeader(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc()); + if (Objects.nonNull(quotaResponse.getActiveRule())) { + response.addHeader(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, quotaResponse.getActiveRule().getName().getValue()); + } return; } // Unirate diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java index 5b10a90b4..bca453505 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java @@ -94,7 +94,9 @@ public class QuotaCheckReactiveFilterTest { return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk")); } else if (serviceName.equals("TestApp3")) { - return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited")); + QuotaResponse response = new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited")); + response.setActiveRule(RateLimitProto.Rule.newBuilder().build()); + return response; } else { return new QuotaResponse(new QuotaResult(null, 0, null)); diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilterTest.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilterTest.java index b5f64f882..f606102ee 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilterTest.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilterTest.java @@ -94,7 +94,9 @@ public class QuotaCheckServletFilterTest { return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk")); } else if (serviceName.equals("TestApp3")) { - return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited")); + QuotaResponse response = new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited")); + response.setActiveRule(RateLimitProto.Rule.newBuilder().build()); + return response; } else { return new QuotaResponse(new QuotaResult(null, 0, null)); diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/HeaderConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/HeaderConstant.java new file mode 100644 index 000000000..feffd0f49 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/HeaderConstant.java @@ -0,0 +1,52 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.common.constant; + +/** + * Built-in system http header fields. + */ +public final class HeaderConstant { + + /** + * The called service returns the real call result of its own processing request. + */ + public static final String INTERNAL_CALLEE_RET_STATUS = "internal-callee-retstatus"; + + /** + * The name of the rule that the current limit/circiutbreaker rule takes effect. + */ + public static final String INTERNAL_ACTIVE_RULE_NAME = "internal-callee-activerule"; + + /** + * The name information of the called service. + */ + public static final String INTERNAL_CALLEE_SERVICE_ID = "internal-callee-serviceid"; + + /** + * The name information of the called instance host. + */ + public static final String INTERNAL_CALLEE_INSTANCE_HOST = "internal-callee-instance-host"; + + /** + * The name information of the called instance port. + */ + public static final String INTERNAL_CALLEE_INSTANCE_PORT = "internal-callee-instance-port"; + + private HeaderConstant() { + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/RequestLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/RequestLabelUtils.java new file mode 100644 index 000000000..d76e5bf4d --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/RequestLabelUtils.java @@ -0,0 +1,34 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.common.util; + +/** + * Request Label Utils. + */ +public final class RequestLabelUtils { + + private RequestLabelUtils() { + } + + public static String convertLabel(String label) { + label = label.replaceAll("\"|\\{|\\}", "") + .replaceAll(",", "|"); + return label; + } + +} diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/README-zh.md b/spring-cloud-tencent-examples/polaris-ratelimit-example/README-zh.md index b03b1d1ed..c7a952f9a 100644 --- a/spring-cloud-tencent-examples/polaris-ratelimit-example/README-zh.md +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/README-zh.md @@ -27,12 +27,14 @@ - 在北极星服务端,可以通过控制台,在命名空间Production下,添加服务RateLimitCalleeService。 - 启动服务被调方: 1. IDE直接启动:找到主类 `RateLimitCalleeService`,执行 main 方法启动应用。 - 2. 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar ratelimit-callee-sevice-${verion}.jar`启动应用。 + 2. 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包 + - 执行 `java -jar ratelimit-callee-sevice-${verion}.jar`启动应用 + - 执行 `java -jar ratelimit-caller-sevice-${verion}.jar`启动应用 - 启动后,可以在北极星控制台上看到注册上来的服务实例信息。 3. 调用服务 - 通过浏览器访问http://127.0.0.1:48081/business/invoke,可以看到以下输出信息: + 通过浏览器访问http://127.0.0.1:58080/business/invoke,可以看到以下输出信息: ```` hello world for ratelimit service 1 hello world for ratelimit service 2 diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/README.md b/spring-cloud-tencent-examples/polaris-ratelimit-example/README.md index b9cf2bacd..5df8f78ba 100644 --- a/spring-cloud-tencent-examples/polaris-ratelimit-example/README.md +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/README.md @@ -32,13 +32,14 @@ Examples provided by Polaris all support to run at IDE, or compile and run with - Launch callee server: 1. Launch IDE directly: First find `RateLimitCalleeService`, execute main then launch application - 2. compile package then launch: first execute `mvn clean package` compile the package, then execute `java -jar ratelimit-callee-sevice-${verion}.jar` execute the application - + 2. compile package then launch: first execute `mvn clean package` compile the package + - then execute `java -jar ratelimit-callee-sevice-${verion}.jar` execute the application + - then execute `java -jar ratelimit-caller-sevice-${verion}.jar` execute the application - After launching, one can watch server instance from Polaris control panel 3. Invoke Service - After visiting http://127.0.0.1:48081/business/invoke, one can see the following information: + After visiting http://127.0.0.1:58080/business/invoke, one can see the following information: ```` hello world for ratelimit service 1 @@ -61,7 +62,7 @@ Examples provided by Polaris all support to run at IDE, or compile and run with ![](polaris-ratelimit-ui.png) 5. Verify rate limit result - continue visit http://127.0.0.1:48081/business/invoke, one can see, after 10 invokes, rate limit will start: + continue visit http://127.0.0.1:68080/business/invoke, one can see, after 10 invokes, rate limit will start: ```` hello world for ratelimit service 1 diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/pom.xml b/spring-cloud-tencent-examples/polaris-ratelimit-example/pom.xml index 0b5cae8c8..de8c7941a 100644 --- a/spring-cloud-tencent-examples/polaris-ratelimit-example/pom.xml +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/pom.xml @@ -16,6 +16,7 @@ ratelimit-callee-service + ratelimit-caller-service diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/BusinessController.java b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/BusinessController.java index fc8a51f3f..75cb35a40 100644 --- a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/BusinessController.java +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/BusinessController.java @@ -48,6 +48,7 @@ import org.springframework.web.reactive.function.client.WebClientResponseExcepti * * @author Haotian Zhang */ +@Deprecated @RestController @RequestMapping("/business") public class BusinessController { diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/pom.xml b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/pom.xml new file mode 100644 index 000000000..a2b3f4dcc --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + polaris-ratelimit-example + com.tencent.cloud + ${revision} + ../pom.xml + + + ratelimit-caller-service + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-webflux + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-discovery + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-ratelimit + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/src/main/java/com/tencent/cloud/ratelimit/example/service/caller/Controller.java b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/src/main/java/com/tencent/cloud/ratelimit/example/service/caller/Controller.java new file mode 100644 index 000000000..291d509b2 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/src/main/java/com/tencent/cloud/ratelimit/example/service/caller/Controller.java @@ -0,0 +1,171 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.ratelimit.example.service.caller; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; + +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.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.HttpClientErrorException.TooManyRequests; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +@RestController +@RequestMapping("/business") +public class Controller { + + private static final Logger LOG = LoggerFactory.getLogger(Controller.class); + + private final AtomicInteger index = new AtomicInteger(0); + private final AtomicLong lastTimestamp = new AtomicLong(0); + @Autowired + private RestTemplate restTemplate; + @Autowired + private WebClient.Builder webClientBuilder; + + private String appName = "RateLimitCalleeService"; + + /** + * Get information. + * @return information + */ + @GetMapping("/info") + public String info() { + return "hello world for ratelimit service " + index.incrementAndGet(); + } + + @GetMapping("/info/webclient") + public Mono infoWebClient() { + return Mono.just("hello world for ratelimit service " + index.incrementAndGet()); + } + + @GetMapping("/invoke/webclient") + public String invokeInfoWebClient() throws InterruptedException, ExecutionException { + StringBuffer builder = new StringBuffer(); + WebClient webClient = webClientBuilder.baseUrl("http://" + appName).build(); + List> monoList = new ArrayList<>(); + for (int i = 0; i < 30; i++) { + Mono response = webClient.get() + .uri(uriBuilder -> uriBuilder + .path("/business/info/webclient") + .queryParam("yyy", "yyy") + .build() + ) + .header("xxx", "xxx") + .retrieve() + .bodyToMono(String.class) + .doOnSuccess(s -> builder.append(s + "\n")) + .doOnError(e -> { + if (e instanceof WebClientResponseException) { + if (((WebClientResponseException) e).getRawStatusCode() == 429) { + builder.append("TooManyRequests ").append(index.incrementAndGet() + "\n"); + } + } + }) + .onErrorReturn(""); + monoList.add(response); + } + for (Mono mono : monoList) { + mono.toFuture().get(); + } + index.set(0); + return builder.toString(); + } + + /** + * Get information 30 times per 1 second. + * + * @return result of 30 calls. + * @throws InterruptedException exception + */ + @GetMapping("/invoke") + public String invokeInfo() throws InterruptedException { + StringBuffer builder = new StringBuffer(); + CountDownLatch count = new CountDownLatch(30); + for (int i = 0; i < 30; i++) { + new Thread(() -> { + try { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add("xxx", "xxx"); + ResponseEntity entity = restTemplate.exchange( + "http://" + appName + "/business/info?yyy={yyy}", + HttpMethod.GET, + new HttpEntity<>(httpHeaders), + String.class, + "yyy" + ); + builder.append(entity.getBody() + "\n"); + } + catch (RestClientException e) { + if (e instanceof TooManyRequests) { + builder.append("TooManyRequests ").append(index.incrementAndGet() + "\n"); + } + else { + throw e; + } + } + catch (Exception e) { + e.printStackTrace(); + } + finally { + count.countDown(); + } + }).start(); + } + count.await(); + index.set(0); + return builder.toString(); + } + + /** + * Get information with unirate. + * + * @return information + */ + @GetMapping("/unirate") + public String unirate() { + long currentTimestamp = System.currentTimeMillis(); + long lastTime = lastTimestamp.get(); + if (lastTime != 0) { + LOG.info("Current timestamp:" + currentTimestamp + ", diff from last timestamp:" + (currentTimestamp - lastTime)); + } + else { + LOG.info("Current timestamp:" + currentTimestamp); + } + lastTimestamp.set(currentTimestamp); + return "hello world for ratelimit service with diff from last request:" + (currentTimestamp - lastTime) + "ms."; + } + +} diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/src/main/java/com/tencent/cloud/ratelimit/example/service/caller/RateLimitCallerService.java b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/src/main/java/com/tencent/cloud/ratelimit/example/service/caller/RateLimitCallerService.java new file mode 100644 index 000000000..b4d550e4a --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/src/main/java/com/tencent/cloud/ratelimit/example/service/caller/RateLimitCallerService.java @@ -0,0 +1,52 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.ratelimit.example.service.caller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; + +@SpringBootApplication +public class RateLimitCallerService { + + private static final Logger LOG = LoggerFactory.getLogger(RateLimitCallerService.class); + + public static void main(String[] args) { + SpringApplication.run(RateLimitCallerService.class, args); + } + + @Bean + @LoadBalanced + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + + @LoadBalanced + @Bean + WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..d977986ed --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-caller-service/src/main/resources/bootstrap.yml @@ -0,0 +1,20 @@ +server: + port: 58080 +spring: + application: + name: RateLimitCallerService + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + enabled: true + +management: + endpoints: + web: + exposure: + include: + - polaris-ratelimit +logging: + level: + com.tencent.cloud.polaris: debug diff --git a/spring-cloud-tencent-rpc-enhancement/pom.xml b/spring-cloud-tencent-rpc-enhancement/pom.xml index b4560f070..bf566a053 100644 --- a/spring-cloud-tencent-rpc-enhancement/pom.xml +++ b/spring-cloud-tencent-rpc-enhancement/pom.xml @@ -50,12 +50,24 @@ true + + org.springframework.cloud + spring-cloud-gateway-server + true + + com.tencent.polaris polaris-test-common test + + org.springframework.boot + spring-boot-starter-webflux + true + + org.springframework.boot spring-boot-starter-web diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java index 599b2cc76..ad47fde2b 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java @@ -19,13 +19,18 @@ package com.tencent.cloud.rpc.enhancement; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Objects; +import com.tencent.cloud.common.constant.HeaderConstant; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.pojo.RetStatus; +import com.tencent.polaris.api.utils.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; @@ -116,4 +121,31 @@ public abstract class AbstractPolarisReporterAdapter { // DEFAULT RETURN FALSE. return false; } + + protected RetStatus getRetStatusFromRequest(HttpHeaders headers, RetStatus defaultVal) { + if (headers.containsKey(HeaderConstant.INTERNAL_CALLEE_RET_STATUS)) { + List values = headers.get(HeaderConstant.INTERNAL_CALLEE_RET_STATUS); + if (CollectionUtils.isNotEmpty(values)) { + String retStatusVal = com.tencent.polaris.api.utils.StringUtils.defaultString(values.get(0)); + if (Objects.equals(retStatusVal, RetStatus.RetFlowControl.getDesc())) { + return RetStatus.RetFlowControl; + } + if (Objects.equals(retStatusVal, RetStatus.RetReject.getDesc())) { + return RetStatus.RetReject; + } + } + } + return defaultVal; + } + + protected String getActiveRuleNameFromRequest(HttpHeaders headers) { + if (headers.containsKey(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME)) { + Collection values = headers.get(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME); + if (CollectionUtils.isNotEmpty(values)) { + String val = com.tencent.polaris.api.utils.StringUtils.defaultString(new ArrayList<>(values).get(0)); + return val; + } + } + return ""; + } } 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 53bd5f414..c26d1269b 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 @@ -30,7 +30,11 @@ import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.ExceptionPolarisR import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.SuccessPolarisReporter; import com.tencent.cloud.rpc.enhancement.resttemplate.BlockingLoadBalancerClientAspect; import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter; +import com.tencent.cloud.rpc.enhancement.scg.EnhancedPolarisHttpClientCustomizer; +import com.tencent.cloud.rpc.enhancement.scg.EnhancedPolarisHttpHeadersFilter; +import com.tencent.cloud.rpc.enhancement.webclient.EnhancedWebClientReporter; import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; @@ -42,11 +46,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.gateway.config.HttpClientCustomizer; +import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Role; import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; /** * Auto Configuration for Polaris {@link feign.Feign} OR {@link RestTemplate} which can automatically bring in the call @@ -89,14 +96,16 @@ public class RpcEnhancementAutoConfiguration { @Bean public SuccessPolarisReporter successPolarisReporter(RpcEnhancementReporterProperties properties, - @Autowired(required = false) ConsumerAPI consumerAPI) { - return new SuccessPolarisReporter(properties, consumerAPI); + @Autowired(required = false) SDKContext context, + @Autowired(required = false) ConsumerAPI consumerAPI) { + return new SuccessPolarisReporter(properties, context, consumerAPI); } @Bean public ExceptionPolarisReporter exceptionPolarisReporter(RpcEnhancementReporterProperties properties, - @Autowired(required = false) ConsumerAPI consumerAPI) { - return new ExceptionPolarisReporter(properties, consumerAPI); + @Autowired(required = false) SDKContext context, + @Autowired(required = false) ConsumerAPI consumerAPI) { + return new ExceptionPolarisReporter(properties, context, consumerAPI); } } } @@ -117,8 +126,8 @@ public class RpcEnhancementAutoConfiguration { @Bean public EnhancedRestTemplateReporter enhancedRestTemplateReporter( - RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { - return new EnhancedRestTemplateReporter(properties, consumerAPI); + RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { + return new EnhancedRestTemplateReporter(properties, context, consumerAPI); } @Bean @@ -137,4 +146,43 @@ public class RpcEnhancementAutoConfiguration { return new BlockingLoadBalancerClientAspect(); } } + + /** + * Configuration for Polaris {@link org.springframework.web.reactive.function.client.WebClient} which can automatically bring in the call + * results for reporting. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "org.springframework.web.reactive.function.client.WebClient") + protected static class PolarisWebClientAutoConfiguration { + + @Bean + public ExchangeFilterFunction exchangeFilterFunction( + RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { + return new EnhancedWebClientReporter(properties, context, consumerAPI); + } + } + + /** + * Configuration for Polaris {@link org.springframework.web.reactive.function.client.WebClient} which can automatically bring in the call + * results for reporting. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "org.springframework.cloud.gateway.config.GatewayAutoConfiguration") + @Role(RootBeanDefinition.ROLE_INFRASTRUCTURE) + protected static class PolarisGatewayAutoConfiguration { + + @Bean + @ConditionalOnClass(name = {"org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter"}) + public HttpHeadersFilter enhancedPolarisHttpHeadersFilter() { + return new EnhancedPolarisHttpHeadersFilter(); + } + + @Bean + @ConditionalOnClass(name = {"org.springframework.cloud.gateway.config.HttpClientCustomizer"}) + public HttpClientCustomizer httpClientCustomizer( + RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { + return new EnhancedPolarisHttpClientCustomizer(properties, context, consumerAPI); + } + + } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporter.java index b46f1435c..487c64134 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporter.java @@ -18,7 +18,9 @@ package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter; import java.net.SocketTimeoutException; +import java.util.ArrayList; +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin; @@ -26,28 +28,36 @@ import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; import feign.Request; import feign.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; +import org.springframework.http.HttpHeaders; /** * Polaris reporter when feign call fails. * * @author Haotian Zhang */ -public class ExceptionPolarisReporter implements EnhancedFeignPlugin { +public class ExceptionPolarisReporter extends AbstractPolarisReporterAdapter implements EnhancedFeignPlugin { private static final Logger LOG = LoggerFactory.getLogger(ExceptionPolarisReporter.class); private final RpcEnhancementReporterProperties reporterProperties; private final ConsumerAPI consumerAPI; + private final SDKContext context; + + public ExceptionPolarisReporter(RpcEnhancementReporterProperties reporterProperties, + SDKContext context, ConsumerAPI consumerAPI) { + super(reporterProperties); this.reporterProperties = reporterProperties; + this.context = context; this.consumerAPI = consumerAPI; } @@ -78,7 +88,13 @@ public class ExceptionPolarisReporter implements EnhancedFeignPlugin { } LOG.debug("Will report result of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.", retStatus.name(), request.httpMethod() .name(), request.url(), response.status(), delay); - ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(request, response, delay, retStatus); + ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(this.context, request, response, + delay, retStatus, serviceCallResult -> { + HttpHeaders headers = new HttpHeaders(); + response.headers().forEach((s, strings) -> headers.addAll(s, new ArrayList<>(strings))); + serviceCallResult.setRetStatus(getRetStatusFromRequest(headers, serviceCallResult.getRetStatus())); + serviceCallResult.setRuleName(getActiveRuleNameFromRequest(headers)); + }); consumerAPI.updateServiceCallResult(resultRequest); } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtils.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtils.java index c7e97404a..7d6a94b0e 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtils.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtils.java @@ -21,13 +21,17 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; import java.util.Collection; +import java.util.Objects; +import java.util.function.Consumer; import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.RequestLabelUtils; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.api.rpc.ServiceCallResult; import com.tencent.polaris.api.utils.CollectionUtils; +import com.tencent.polaris.client.api.SDKContext; import feign.Request; import feign.RequestTemplate; import feign.Response; @@ -49,7 +53,8 @@ public final class ReporterUtils { private ReporterUtils() { } - public static ServiceCallResult createServiceCallResult(final Request request, final Response response, long delay, RetStatus retStatus) { + public static ServiceCallResult createServiceCallResult(final SDKContext context, final Request request, + final Response response, long delay, RetStatus retStatus, final Consumer consumer) { ServiceCallResult resultRequest = new ServiceCallResult(); resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE); @@ -65,7 +70,7 @@ public final class ReporterUtils { catch (UnsupportedEncodingException e) { LOGGER.error("unsupported charset exception " + UTF_8, e); } - resultRequest.setLabels(convertLabel(label)); + resultRequest.setLabels(RequestLabelUtils.convertLabel(label)); } URI uri = URI.create(request.url()); resultRequest.setMethod(uri.getPath()); @@ -82,16 +87,14 @@ public final class ReporterUtils { if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) { resultRequest.setCallerService(new ServiceKey(sourceNamespace, sourceService)); } + if (Objects.nonNull(context)) { + resultRequest.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP()); + } resultRequest.setHost(uri.getHost()); // -1 means access directly by url, and use http default port number 80 resultRequest.setPort(uri.getPort() == -1 ? 80 : uri.getPort()); - + consumer.accept(resultRequest); return resultRequest; } - private static String convertLabel(String label) { - label = label.replaceAll("\"|\\{|\\}", "") - .replaceAll(",", "|"); - return label; - } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java index 54a389c8c..eaa0ae916 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java @@ -17,6 +17,8 @@ package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter; +import java.util.ArrayList; + import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; @@ -25,12 +27,14 @@ import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; import feign.Request; import feign.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; /** @@ -44,8 +48,11 @@ public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter imple private final ConsumerAPI consumerAPI; - public SuccessPolarisReporter(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { + private final SDKContext context; + + public SuccessPolarisReporter(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { super(properties); + this.context = context; this.consumerAPI = consumerAPI; } @@ -75,7 +82,13 @@ public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter imple } LOG.debug("Will report result of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.", retStatus.name(), request.httpMethod() .name(), request.url(), response.status(), delay); - ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(request, response, delay, retStatus); + ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(this.context, request, response, + delay, retStatus, serviceCallResult -> { + HttpHeaders headers = new HttpHeaders(); + response.headers().forEach((s, strings) -> headers.addAll(s, new ArrayList<>(strings))); + serviceCallResult.setRetStatus(getRetStatusFromRequest(headers, serviceCallResult.getRetStatus())); + serviceCallResult.setRuleName(getActiveRuleNameFromRequest(headers)); + }); consumerAPI.updateServiceCallResult(resultRequest); } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java index 2c3ea1fe2..935cd3219 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java @@ -23,10 +23,12 @@ import java.net.URI; import java.net.URLDecoder; import java.util.List; import java.util.Map; +import java.util.Objects; import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.util.RequestLabelUtils; import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; import com.tencent.polaris.api.core.ConsumerAPI; @@ -34,6 +36,7 @@ import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.api.rpc.ServiceCallResult; import com.tencent.polaris.api.utils.CollectionUtils; +import com.tencent.polaris.client.api.SDKContext; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,10 +69,12 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter public static final String HEADER_HAS_ERROR = "X-SCT-Has-Error"; private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedRestTemplateReporter.class); private final ConsumerAPI consumerAPI; + private final SDKContext context; private ResponseErrorHandler delegateHandler; - public EnhancedRestTemplateReporter(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { + public EnhancedRestTemplateReporter(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { super(properties); + this.context = context; this.consumerAPI = consumerAPI; } @@ -149,6 +154,11 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter if (apply(response.getStatusCode())) { resultRequest.setRetStatus(RetStatus.RetFail); } + resultRequest.setRetStatus(getRetStatusFromRequest(response.getHeaders(), resultRequest.getRetStatus())); + resultRequest.setRuleName(getActiveRuleNameFromRequest(response.getHeaders())); + if (Objects.nonNull(context)) { + resultRequest.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP()); + } List labels = response.getHeaders().get(RouterConstant.ROUTER_LABEL_HEADER); if (CollectionUtils.isNotEmpty(labels)) { @@ -159,7 +169,7 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter catch (UnsupportedEncodingException e) { LOGGER.error("unsupported charset exception " + UTF_8, e); } - resultRequest.setLabels(convertLabel(label)); + resultRequest.setLabels(RequestLabelUtils.convertLabel(label)); } // processing report with consumerAPI . @@ -172,12 +182,6 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter } } - private String convertLabel(String label) { - label = label.replaceAll("\"|\\{|\\}", "") - .replaceAll(",", "|"); - return label; - } - private void invokeDelegateHandler(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { if (realHasError(response)) { delegateHandler.handleError(url, method, response); @@ -227,4 +231,5 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter protected void setDelegateHandler(ResponseErrorHandler delegateHandler) { this.delegateHandler = delegateHandler; } + } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClient.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClient.java new file mode 100644 index 000000000..33b2b556c --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClient.java @@ -0,0 +1,173 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.rpc.enhancement.scg; + +import java.net.SocketTimeoutException; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; + +import com.tencent.cloud.common.constant.HeaderConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.pojo.RetStatus; +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; +import io.netty.handler.codec.http.HttpHeaders; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.netty.http.client.HttpClient; +import reactor.netty.http.client.HttpClientConfig; +import reactor.netty.http.client.HttpClientResponse; + +import org.springframework.http.HttpStatus; + +public class EnhancedPolarisHttpClient extends HttpClient { + + private static final Logger LOG = LoggerFactory.getLogger(EnhancedPolarisHttpClient.class); + + private final RpcEnhancementReporterProperties properties; + private final SDKContext context; + private final ConsumerAPI consumerAPI; + private final Reporter adapter; + private final BiConsumer handler = new BiConsumer() { + @Override + public void accept(HttpClientResponse httpClientResponse, Throwable throwable) { + if (Objects.isNull(consumerAPI)) { + return; + } + HttpHeaders responseHeaders = httpClientResponse.responseHeaders(); + + ServiceCallResult result = new ServiceCallResult(); + result.setCallerService(new ServiceKey(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE)); + result.setNamespace(MetadataContext.LOCAL_NAMESPACE); + + Map metadata = MetadataContextHolder.get().getLoadbalancerMetadata(); + result.setDelay(System.currentTimeMillis() - Long.parseLong(metadata.get("startTime"))); + result.setService(metadata.get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID)); + result.setHost(metadata.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST)); + result.setPort(Integer.parseInt(metadata.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT))); + RetStatus status = RetStatus.RetSuccess; + if (Objects.isNull(throwable)) { + if (EnhancedPolarisHttpClient.this.adapter.apply(HttpStatus.valueOf(httpClientResponse.status() + .code()))) { + status = RetStatus.RetFail; + } + org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders(); + responseHeaders.forEach(entry -> headers.add(entry.getKey(), entry.getValue())); + status = adapter.getRetStatusFromRequest(headers, status); + result.setRuleName(adapter.getActiveRuleNameFromRequest(headers)); + } + else { + if (throwable instanceof SocketTimeoutException) { + status = RetStatus.RetTimeout; + } + } + result.setMethod(httpClientResponse.uri()); + result.setRetCode(httpClientResponse.status().code()); + result.setRetStatus(status); + if (Objects.nonNull(context)) { + result.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP()); + } + try { + consumerAPI.updateServiceCallResult(result); + } + catch (Throwable ex) { + LOG.error("update service call result fail", ex); + } + } + }; + private HttpClient target; + + public EnhancedPolarisHttpClient( + HttpClient client, + RpcEnhancementReporterProperties properties, + SDKContext context, + ConsumerAPI consumerAPI) { + this.properties = properties; + this.context = context; + this.consumerAPI = consumerAPI; + this.target = client; + this.adapter = new Reporter(properties); + this.registerReportHandler(); + } + + @Override + public HttpClientConfig configuration() { + return target.configuration(); + } + + @Override + protected HttpClient duplicate() { + return new EnhancedPolarisHttpClient(target, properties, context, consumerAPI); + } + + private void registerReportHandler() { + target = target.doOnRequest((request, connection) -> { + String serviceId = request.requestHeaders().get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID); + String host = request.requestHeaders().get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST); + String port = request.requestHeaders().get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT); + if (StringUtils.isNotBlank(serviceId)) { + MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, serviceId); + MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST, host); + MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT, port); + MetadataContextHolder.get().setLoadbalancer("startTime", System.currentTimeMillis() + ""); + } + + request.requestHeaders().remove(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID); + request.requestHeaders().remove(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST); + request.requestHeaders().remove(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT); + }); + target = target.doOnResponse((httpClientResponse, connection) -> handler.accept(httpClientResponse, null)); + target = target.doOnResponseError(handler); + } + + + private static class Reporter extends AbstractPolarisReporterAdapter { + + /** + * Constructor With {@link RpcEnhancementReporterProperties} . + * + * @param reportProperties instance of {@link RpcEnhancementReporterProperties}. + */ + protected Reporter(RpcEnhancementReporterProperties reportProperties) { + super(reportProperties); + } + + @Override + public boolean apply(HttpStatus httpStatus) { + return super.apply(httpStatus); + } + + @Override + public RetStatus getRetStatusFromRequest(org.springframework.http.HttpHeaders headers, RetStatus defaultVal) { + return super.getRetStatusFromRequest(headers, defaultVal); + } + + @Override + public String getActiveRuleNameFromRequest(org.springframework.http.HttpHeaders headers) { + return super.getActiveRuleNameFromRequest(headers); + } + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizer.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizer.java new file mode 100644 index 000000000..630feb349 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizer.java @@ -0,0 +1,43 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.rpc.enhancement.scg; + +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; +import reactor.netty.http.client.HttpClient; + +import org.springframework.cloud.gateway.config.HttpClientCustomizer; + +public class EnhancedPolarisHttpClientCustomizer implements HttpClientCustomizer { + + private final RpcEnhancementReporterProperties properties; + private final SDKContext context; + private final ConsumerAPI consumerAPI; + + public EnhancedPolarisHttpClientCustomizer(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { + this.properties = properties; + this.context = context; + this.consumerAPI = consumerAPI; + } + + @Override + public HttpClient customize(HttpClient httpClient) { + return new EnhancedPolarisHttpClient(httpClient, properties, context, consumerAPI); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilter.java new file mode 100644 index 000000000..a251b67a1 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilter.java @@ -0,0 +1,71 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.rpc.enhancement.scg; + +import java.util.List; + +import com.tencent.cloud.common.constant.HeaderConstant; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; +import org.springframework.http.HttpHeaders; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR; + +public class EnhancedPolarisHttpHeadersFilter implements HttpHeadersFilter { + + public EnhancedPolarisHttpHeadersFilter() { + } + + @Override + public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) { + Response serviceInstanceResponse = exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR); + if (serviceInstanceResponse == null || !serviceInstanceResponse.hasServer()) { + return input; + } + ServiceInstance instance = serviceInstanceResponse.getServer(); + write(input, HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, instance.getServiceId(), true); + write(input, HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST, instance.getHost(), true); + write(input, HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT, instance.getPort() + "", true); + return input; + } + + @Override + public boolean supports(Type type) { + return Type.REQUEST.equals(type); + } + + private void write(HttpHeaders headers, String name, String value, boolean append) { + if (value == null) { + return; + } + if (append) { + headers.add(name, value); + // these headers should be treated as a single comma separated header + List values = headers.get(name); + String delimitedValue = StringUtils.collectionToCommaDelimitedString(values); + headers.set(name, delimitedValue); + } + else { + headers.set(name, value); + } + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporter.java new file mode 100644 index 000000000..33737cdd7 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporter.java @@ -0,0 +1,144 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.rpc.enhancement.webclient; + +import java.io.UnsupportedEncodingException; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URLDecoder; +import java.util.Collection; +import java.util.Objects; + +import com.tencent.cloud.common.constant.HeaderConstant; +import com.tencent.cloud.common.constant.RouterConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.RequestLabelUtils; +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.pojo.RetStatus; +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.api.utils.CollectionUtils; +import com.tencent.polaris.client.api.SDKContext; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +import org.springframework.http.HttpHeaders; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeFunction; + +import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; + +public class EnhancedWebClientReporter extends AbstractPolarisReporterAdapter implements ExchangeFilterFunction { + + protected static final String METRICS_WEBCLIENT_START_TIME = EnhancedWebClientReporter.class.getName() + + ".START_TIME"; + private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedWebClientReporter.class); + private final ConsumerAPI consumerAPI; + + private final SDKContext context; + + public EnhancedWebClientReporter(RpcEnhancementReporterProperties reportProperties, SDKContext context, ConsumerAPI consumerAPI) { + super(reportProperties); + this.context = context; + this.consumerAPI = consumerAPI; + } + + @Override + public Mono filter(ClientRequest request, ExchangeFunction next) { + return next.exchange(request).as((responseMono) -> instrumentResponse(request, responseMono)) + .contextWrite(this::putStartTime); + } + + Mono instrumentResponse(ClientRequest request, Mono responseMono) { + return Mono.deferContextual((ctx) -> responseMono.doOnEach((signal) -> { + // report result to polaris + if (!reportProperties.isEnabled()) { + return; + } + ServiceCallResult callResult = new ServiceCallResult(); + Long startTime = getStartTime(ctx); + callResult.setDelay(System.currentTimeMillis() - startTime); + + callResult.setNamespace(MetadataContext.LOCAL_NAMESPACE); + callResult.setService(request.headers().getFirst(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID)); + String sourceNamespace = MetadataContext.LOCAL_NAMESPACE; + String sourceService = MetadataContext.LOCAL_SERVICE; + if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) { + callResult.setCallerService(new ServiceKey(sourceNamespace, sourceService)); + } + + Collection labels = request.headers().get(RouterConstant.ROUTER_LABEL_HEADER); + if (CollectionUtils.isNotEmpty(labels) && labels.iterator().hasNext()) { + String label = labels.iterator().next(); + try { + label = URLDecoder.decode(label, UTF_8); + } + catch (UnsupportedEncodingException e) { + LOGGER.error("unsupported charset exception " + UTF_8, e); + } + callResult.setLabels(RequestLabelUtils.convertLabel(label)); + } + + URI uri = request.url(); + callResult.setMethod(uri.getPath()); + callResult.setHost(uri.getHost()); + // -1 means access directly by url, and use http default port number 80 + callResult.setPort(uri.getPort() == -1 ? 80 : uri.getPort()); + if (Objects.nonNull(context)) { + callResult.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP()); + } + + RetStatus retStatus = RetStatus.RetSuccess; + ClientResponse response = signal.get(); + if (Objects.nonNull(response)) { + HttpHeaders headers = response.headers().asHttpHeaders(); + + callResult.setRuleName(getActiveRuleNameFromRequest(headers)); + if (apply(response.statusCode())) { + retStatus = RetStatus.RetFail; + } + retStatus = getRetStatusFromRequest(headers, retStatus); + } + if (signal.isOnError()) { + Throwable throwable = signal.getThrowable(); + if (throwable instanceof SocketTimeoutException) { + retStatus = RetStatus.RetTimeout; + } + } + callResult.setRetStatus(retStatus); + + consumerAPI.updateServiceCallResult(callResult); + })); + } + + private Long getStartTime(ContextView context) { + return context.get(METRICS_WEBCLIENT_START_TIME); + } + + private Context putStartTime(Context context) { + return context.put(METRICS_WEBCLIENT_START_TIME, System.currentTimeMillis()); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapterTest.java index 4c9c8a4e9..351c278ce 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapterTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapterTest.java @@ -17,10 +17,13 @@ package com.tencent.cloud.rpc.enhancement; +import com.tencent.cloud.common.constant.HeaderConstant; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.pojo.RetStatus; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; /** @@ -104,6 +107,48 @@ public class AbstractPolarisReporterAdapterTest { Assertions.assertThat(adapter.apply(HttpStatus.FORBIDDEN)).isEqualTo(true); } + @Test + public void testGetRetStatusFromRequest() { + RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties(); + // Mock Condition + properties.getStatuses().clear(); + properties.getSeries().clear(); + properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR); + + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + + HttpHeaders headers = new HttpHeaders(); + RetStatus ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail); + Assertions.assertThat(ret).isEqualTo(RetStatus.RetFail); + + headers.set(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc()); + ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail); + Assertions.assertThat(ret).isEqualTo(RetStatus.RetFlowControl); + + headers.set(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetReject.getDesc()); + ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail); + Assertions.assertThat(ret).isEqualTo(RetStatus.RetReject); + } + + @Test + public void testGetActiveRuleNameFromRequest() { + RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties(); + // Mock Condition + properties.getStatuses().clear(); + properties.getSeries().clear(); + properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR); + + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + + HttpHeaders headers = new HttpHeaders(); + String ruleName = adapter.getActiveRuleNameFromRequest(headers); + Assertions.assertThat(ruleName).isEqualTo(""); + + headers.set(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, "mock_rule"); + ruleName = adapter.getActiveRuleNameFromRequest(headers); + Assertions.assertThat(ruleName).isEqualTo("mock_rule"); + } + /** * Simple Polaris CircuitBreak Adapter Implements . */ 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 199fd36b7..e768dd914 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 @@ -50,7 +50,7 @@ public class RpcEnhancementAutoConfigurationTest { RpcEnhancementAutoConfiguration.class, PolarisRestTemplateAutoConfigurationTester.class, FeignLoadBalancerAutoConfiguration.class)) - .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true", "spring.application.name=test"); + .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true", "spring.application.name=test", "spring.cloud.gateway.enabled=false"); @Test public void testDefaultInitialization() { diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementReporterPropertiesTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementReporterPropertiesTest.java index 111c031c5..e3483decc 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementReporterPropertiesTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementReporterPropertiesTest.java @@ -38,7 +38,10 @@ import static org.springframework.http.HttpStatus.Series.SERVER_ERROR; * @author Haotian Zhang */ @ExtendWith(SpringExtension.class) -@SpringBootTest(classes = RpcEnhancementReporterPropertiesTest.TestApplication.class, properties = "spring.application.name=test") +@SpringBootTest(classes = RpcEnhancementReporterPropertiesTest.TestApplication.class, properties = { + "spring.application.name=test", + "spring.cloud.gateway.enabled=false" +}) @ActiveProfiles("test") public class RpcEnhancementReporterPropertiesTest { diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClientTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClientTest.java index 76027f6dc..e1c8eacf6 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClientTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClientTest.java @@ -51,7 +51,7 @@ import static org.mockito.Mockito.mock; */ @ExtendWith(SpringExtension.class) @SpringBootTest(classes = EnhancedFeignClientTest.TestApplication.class, - properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"}) + properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp", "spring.cloud.gateway.enabled=false"}) public class EnhancedFeignClientTest { @Test diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporterTest.java index 9e926703a..617732d73 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporterTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporterTest.java @@ -18,17 +18,24 @@ package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter; import java.util.HashMap; +import java.util.function.Consumer; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; import feign.Request; +import feign.RequestTemplate; import feign.Response; +import feign.Target; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -37,9 +44,12 @@ import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -54,6 +64,7 @@ import static org.mockito.Mockito.verify; public class ExceptionPolarisReporterTest { private static MockedStatic mockedReporterUtils; + private static MockedStatic mockedApplicationContextAwareUtils; @Mock private ConsumerAPI consumerAPI; @Mock @@ -63,16 +74,27 @@ public class ExceptionPolarisReporterTest { @BeforeAll static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class); - mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(Request.class), any(Response.class), anyLong(), any(RetStatus.class))) - .thenReturn(mock(ServiceCallResult.class)); + mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class), + any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class))) + .thenReturn(new ServiceCallResult()); } @AfterAll static void afterAll() { + mockedApplicationContextAwareUtils.close(); mockedReporterUtils.close(); } + @BeforeEach + void setUp() { + MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST; + MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER; + } + @Test public void testGetName() { assertThat(exceptionPolarisReporter.getName()).isEqualTo(ExceptionPolarisReporter.class.getName()); @@ -100,6 +122,33 @@ public class ExceptionPolarisReporterTest { doReturn(true).when(reporterProperties).isEnabled(); exceptionPolarisReporter.run(context); verify(context, times(1)).getRequest(); + + + try { + mockedReporterUtils.close(); + // mock target + Target target = mock(Target.class); + doReturn(SERVICE_PROVIDER).when(target).name(); + + // mock RequestTemplate.class + RequestTemplate requestTemplate = new RequestTemplate(); + requestTemplate.feignTarget(target); + + EnhancedFeignContext feignContext = new EnhancedFeignContext(); + request = Request.create(Request.HttpMethod.GET, "/", new HashMap<>(), null, null, requestTemplate); + response = Response.builder() + .request(request) + .build(); + feignContext.setRequest(request); + feignContext.setResponse(response); + exceptionPolarisReporter.run(feignContext); + } + finally { + mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class); + mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class), + any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class))) + .thenReturn(new ServiceCallResult()); + } } @Test diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtilsTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtilsTest.java index 409ac5f89..0ba60f5fe 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtilsTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtilsTest.java @@ -23,8 +23,12 @@ import java.net.URLEncoder; import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.polaris.api.config.Configuration; +import com.tencent.polaris.api.config.global.APIConfig; +import com.tencent.polaris.api.config.global.GlobalConfig; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; import feign.Request; import feign.RequestTemplate; import feign.Response; @@ -99,7 +103,9 @@ public class ReporterUtilsTest { Response response = mock(Response.class); doReturn(502).when(response).status(); - ServiceCallResult serviceCallResult = ReporterUtils.createServiceCallResult(request, response, 10L, RetStatus.RetSuccess); + ServiceCallResult serviceCallResult = ReporterUtils.createServiceCallResult(mockSDKContext(), request, response, 10L, RetStatus.RetSuccess, result -> { + + }); assertThat(serviceCallResult.getNamespace()).isEqualTo(NAMESPACE_TEST); assertThat(serviceCallResult.getService()).isEqualTo(SERVICE_PROVIDER); assertThat(serviceCallResult.getHost()).isEqualTo("1.1.1.1"); @@ -112,4 +118,17 @@ public class ReporterUtilsTest { assertThat(serviceCallResult.getRetCode()).isEqualTo(502); assertThat(serviceCallResult.getDelay()).isEqualTo(10L); } + + public static SDKContext mockSDKContext() { + APIConfig apiConfig = mock(APIConfig.class); + doReturn("127.0.0.1").when(apiConfig).getBindIP(); + GlobalConfig globalConfig = mock(GlobalConfig.class); + doReturn(apiConfig).when(globalConfig).getAPI(); + Configuration configuration = mock(Configuration.class); + doReturn(globalConfig).when(configuration).getGlobal(); + SDKContext context = mock(SDKContext.class); + doReturn(configuration).when(context).getConfig(); + + return context; + } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java index b82eac465..44b667b79 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java @@ -18,17 +18,24 @@ package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter; import java.util.HashMap; +import java.util.function.Consumer; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; import feign.Request; +import feign.RequestTemplate; import feign.Response; +import feign.Target; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -37,9 +44,12 @@ import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -54,6 +64,7 @@ import static org.mockito.Mockito.verify; public class SuccessPolarisReporterTest { private static MockedStatic mockedReporterUtils; + private static MockedStatic mockedApplicationContextAwareUtils; @Mock private ConsumerAPI consumerAPI; @Mock @@ -63,16 +74,27 @@ public class SuccessPolarisReporterTest { @BeforeAll static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class); - mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(Request.class), any(Response.class), anyLong(), any(RetStatus.class))) - .thenReturn(mock(ServiceCallResult.class)); + mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class), + any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class))) + .thenReturn(new ServiceCallResult()); } @AfterAll static void afterAll() { + mockedApplicationContextAwareUtils.close(); mockedReporterUtils.close(); } + @BeforeEach + void setUp() { + MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST; + MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER; + } + @Test public void testGetName() { assertThat(successPolarisReporter.getName()).isEqualTo(SuccessPolarisReporter.class.getName()); @@ -101,6 +123,32 @@ public class SuccessPolarisReporterTest { doReturn(true).when(reporterProperties).isEnabled(); successPolarisReporter.run(context); verify(context, times(1)).getRequest(); + + try { + mockedReporterUtils.close(); + // mock target + Target target = mock(Target.class); + doReturn(SERVICE_PROVIDER).when(target).name(); + + // mock RequestTemplate.class + RequestTemplate requestTemplate = new RequestTemplate(); + requestTemplate.feignTarget(target); + + EnhancedFeignContext feignContext = new EnhancedFeignContext(); + request = Request.create(Request.HttpMethod.GET, "/", new HashMap<>(), null, null, requestTemplate); + response = Response.builder() + .request(request) + .build(); + feignContext.setRequest(request); + feignContext.setResponse(response); + successPolarisReporter.run(feignContext); + } + finally { + mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class); + mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class), + any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class))) + .thenReturn(new ServiceCallResult()); + } } @Test diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizerTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizerTest.java new file mode 100644 index 000000000..84056bf69 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizerTest.java @@ -0,0 +1,49 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.rpc.enhancement.scg; + +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import reactor.netty.http.client.HttpClient; + +public class EnhancedPolarisHttpClientCustomizerTest { + + @Test + public void testCustomize() { + RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties(); + properties.setEnabled(true); + properties.getStatuses().clear(); + properties.getSeries().clear(); + + SDKContext context = Mockito.mock(SDKContext.class); + ConsumerAPI consumerAPI = Mockito.mock(ConsumerAPI.class); + + EnhancedPolarisHttpClientCustomizer clientCustomizer = new EnhancedPolarisHttpClientCustomizer(properties, context, consumerAPI); + + HttpClient client = HttpClient.create(); + HttpClient proxyClient = clientCustomizer.customize(client); + + Assertions.assertNotNull(proxyClient); + Assertions.assertEquals(EnhancedPolarisHttpClient.class.getName(), proxyClient.getClass().getName()); + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilterTest.java new file mode 100644 index 000000000..f8c8b7710 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilterTest.java @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.rpc.enhancement.scg; + +import java.util.Collections; + +import com.tencent.cloud.common.constant.HeaderConstant; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.DefaultResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.web.server.ServerWebExchange; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR; + +public class EnhancedPolarisHttpHeadersFilterTest { + + @Test + public void testFilter() { + EnhancedPolarisHttpHeadersFilter filter = new EnhancedPolarisHttpHeadersFilter(); + + ServiceInstance instance = Mockito.mock(ServiceInstance.class); + Mockito.doReturn("mock_service").when(instance).getServiceId(); + Mockito.doReturn("127.0.0.1").when(instance).getHost(); + Mockito.doReturn(8080).when(instance).getPort(); + DefaultResponse response = new DefaultResponse(instance); + + ServerWebExchange exchange = Mockito.mock(ServerWebExchange.class); + Mockito.doReturn(response).when(exchange).getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR); + + HttpHeaders input = new HttpHeaders(); + HttpHeaders headers = filter.filter(input, exchange); + + Assertions.assertEquals(Collections.singletonList("mock_service"), headers.get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID)); + Assertions.assertEquals(Collections.singletonList("127.0.0.1"), headers.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST)); + Assertions.assertEquals(Collections.singletonList("8080"), headers.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT)); + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatPropertiesTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatPropertiesTest.java index 7074a39b6..86c0c74a7 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatPropertiesTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatPropertiesTest.java @@ -40,7 +40,8 @@ public class PolarisStatPropertiesTest { .withPropertyValues("spring.cloud.polaris.stat.path=/xxx") .withPropertyValues("spring.cloud.polaris.stat.pushgateway.enabled=true") .withPropertyValues("spring.cloud.polaris.stat.pushgateway.address=127.0.0.1:9091") - .withPropertyValues("spring.cloud.polaris.stat.pushgateway.push-interval=1000"); + .withPropertyValues("spring.cloud.polaris.stat.pushgateway.push-interval=1000") + .withPropertyValues("spring.cloud.gateway.enabled=false"); @Test public void testDefaultInitialization() { diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/StatConfigModifierTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/StatConfigModifierTest.java index 4ef678597..fbfdcc9f2 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/StatConfigModifierTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/StatConfigModifierTest.java @@ -42,7 +42,8 @@ public class StatConfigModifierTest { .withPropertyValues("spring.cloud.polaris.stat.host=127.0.0.1") .withPropertyValues("spring.cloud.polaris.stat.port=20000") .withPropertyValues("spring.cloud.polaris.stat.path=/xxx") - .withPropertyValues("spring.application.name=test"); + .withPropertyValues("spring.application.name=test") + .withPropertyValues("spring.cloud.gateway.enabled=false"); private final ApplicationContextRunner pushContextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(TestApplication.class)) @@ -51,13 +52,15 @@ public class StatConfigModifierTest { .withPropertyValues("spring.cloud.polaris.stat.pushgateway.enabled=true") .withPropertyValues("spring.cloud.polaris.stat.pushgateway.address=127.0.0.1:9091") .withPropertyValues("spring.cloud.polaris.stat.pushgateway.push-interval=1000") - .withPropertyValues("spring.application.name=test"); + .withPropertyValues("spring.application.name=test") + .withPropertyValues("spring.cloud.gateway.enabled=false"); private final ApplicationContextRunner disabledContextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(TestApplication.class)) .withPropertyValues("spring.cloud.polaris.enabled=true") .withPropertyValues("spring.cloud.polaris.stat.enabled=false") - .withPropertyValues("spring.application.name=test"); + .withPropertyValues("spring.application.name=test") + .withPropertyValues("spring.cloud.gateway.enabled=false"); @Test void testPull() { diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporterTest.java new file mode 100644 index 000000000..0393e6c1c --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporterTest.java @@ -0,0 +1,100 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.rpc.enhancement.webclient; + +import java.net.URI; + +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import reactor.core.publisher.Mono; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; + +import static com.tencent.cloud.rpc.enhancement.webclient.EnhancedWebClientReporter.METRICS_WEBCLIENT_START_TIME; +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.mockito.ArgumentMatchers.anyString; + +public class EnhancedWebClientReporterTest { + + private static final String URI_TEMPLATE_ATTRIBUTE = EnhancedWebClientReporterTest.class.getName() + ".uriTemplate"; + + private static MockedStatic mockedApplicationContextAwareUtils; + + @BeforeAll + static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); + } + + @AfterAll + static void afterAll() { + mockedApplicationContextAwareUtils.close(); + } + + @BeforeEach + void setUp() { + MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST; + MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER; + } + + @Test + public void testInstrumentResponse() { + ClientResponse response = Mockito.mock(ClientResponse.class); + ClientResponse.Headers headers = Mockito.mock(ClientResponse.Headers.class); + Mockito.doReturn(headers).when(response).headers(); + Mockito.doReturn(new HttpHeaders()).when(headers).asHttpHeaders(); + Mono responseMono = Mono.just(response); + ClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create("https://example.org/projects/spring-boot")) + .attribute(URI_TEMPLATE_ATTRIBUTE, "https://example.org/projects/{project}") + .build(); + + ConsumerAPI consumerAPI = Mockito.mock(ConsumerAPI.class); + Mockito.doAnswer(invocationOnMock -> { + ServiceCallResult result = invocationOnMock.getArgument(0, ServiceCallResult.class); + Assertions.assertTrue(result.getDelay() > 0); + return null; + }).when(consumerAPI) + .updateServiceCallResult(Mockito.any(ServiceCallResult.class)); + + RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties(); + properties.setEnabled(true); + properties.getStatuses().clear(); + properties.getSeries().clear(); + EnhancedWebClientReporter reporter = new EnhancedWebClientReporter(properties, null, consumerAPI); + + reporter.instrumentResponse(request, responseMono) + .contextWrite(context -> context.put(METRICS_WEBCLIENT_START_TIME, System.currentTimeMillis())) + .subscribe(); + } + +}