diff --git a/CHANGELOG.md b/CHANGELOG.md index a0e295f4..d8d3a429 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - [feature: optimize polaris-discovery-example/discovery-callee-service, add client-ip return.](https://github.com/Tencent/spring-cloud-tencent/pull/940) - [docs:prevent the release of the final version of the sdk.](https://github.com/Tencent/spring-cloud-tencent/pull/944) +- [feat:support webclient and gateway report call metrics](https://github.com/Tencent/spring-cloud-tencent/pull/946) 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 d1d70b9e..fb152208 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,19 @@ 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 +69,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 d80ae90e..1c789bef 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 cfd7e481..56b633ef 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 4a481e86..ab62b337 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,9 +59,8 @@ 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 protected PolarisCircuitBreakerConfigBuilder configBuilder(String id) { String[] metadata = PolarisCircuitBreakerUtils.resolveCircuitBreakerId(id); 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 b2792737..b73303f7 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 9d9bf911..fdb89b2c 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 885e2917..160ed96a 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() { } @@ -51,4 +63,24 @@ public final class PolarisCircuitBreakerUtils { 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); + } + } } 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 a78b59af..f4bd9ede 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 36976da7..5058a8a2 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; @@ -105,7 +107,8 @@ public class PolarisCircuitBreakerMockServerTest { Configuration configuration = TestUtils.configWithEnvAddress(); CircuitBreakAPI circuitBreakAPI = CircuitBreakAPIFactory.createCircuitBreakAPIByConfig(configuration); - PolarisCircuitBreakerFactory polarisCircuitBreakerFactory = new PolarisCircuitBreakerFactory(circuitBreakAPI); + ConsumerAPI consumerAPI = DiscoveryAPIFactory.createConsumerAPIByConfig(configuration); + 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/utils/PolarisCircuitBreakerUtilsTests.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/utils/PolarisCircuitBreakerUtilsTests.java new file mode 100644 index 00000000..0c282047 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/utils/PolarisCircuitBreakerUtilsTests.java @@ -0,0 +1,72 @@ +/* + * 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.utils; + +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.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils; +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/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 00000000..cee59298 --- /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 f6e34726..dab843f9 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 @@ -23,6 +23,7 @@ import java.time.Duration; import java.util.Objects; import java.util.Set; +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; @@ -30,6 +31,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; @@ -115,6 +117,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 c9ed43f0..d6839658 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 @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.Objects; import java.util.Set; +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; @@ -29,6 +30,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; @@ -109,6 +111,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 5b10a90b..352ebb32 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 @@ -27,7 +27,9 @@ import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.StringValue; import com.google.protobuf.util.JsonFormat; +import com.tencent.cloud.common.constant.HeaderConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; @@ -94,7 +96,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().setName(StringValue.newBuilder().setValue("MOCK_RULE").build()).build()); + return response; } else { return new QuotaResponse(new QuotaResult(null, 0, null)); @@ -181,6 +185,7 @@ public class QuotaCheckReactiveFilterTest { ServerHttpResponse response = testApp3Exchange.getResponse(); assertThat(response.getRawStatusCode()).isEqualTo(419); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE); + assertThat(response.getHeaders().get(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME)).isEqualTo(Collections.singletonList("MOCK_RULE")); // Exception MetadataContext.LOCAL_SERVICE = "TestApp4"; 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 7740fc3b..5b5b2016 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 @@ -27,7 +27,9 @@ import java.util.Collections; import java.util.stream.Collectors; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.StringValue; import com.google.protobuf.util.JsonFormat; +import com.tencent.cloud.common.constant.HeaderConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; @@ -93,7 +95,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().setName(StringValue.newBuilder().setValue("MOCK_RULE").build()).build()); + return response; } else { return new QuotaResponse(new QuotaResult(null, 0, null)); @@ -179,6 +183,7 @@ public class QuotaCheckServletFilterTest { quotaCheckServletFilter.doFilterInternal(request, testApp3Response, filterChain); assertThat(testApp3Response.getStatus()).isEqualTo(419); assertThat(testApp3Response.getContentAsString()).isEqualTo("RejectRequestTips提示消息"); + assertThat(testApp3Response.getHeader(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME)).isEqualTo("MOCK_RULE"); MockHttpServletResponse testApp3Response2 = new MockHttpServletResponse(); quotaCheckWithHtmlRejectTipsServletFilter.doFilterInternal(request, testApp3Response2, filterChain); 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 00000000..feffd0f4 --- /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 00000000..d76e5bf4 --- /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-plugin-starters/spring-cloud-tencent-pushgateway-plugin/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-pushgateway-plugin/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 82872aef..00000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-pushgateway-plugin/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.cloud.bootstrap.BootstrapConfiguration=\ - com.tencent.cloud.plugin.pushgateway.PolarisStatPushGatewayBootstrapConfiguration diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-pushgateway-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-pushgateway-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index ffba520c..00000000 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-pushgateway-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.tencent.cloud.plugin.pushgateway.PolarisStatPushGatewayAutoConfiguration diff --git a/spring-cloud-tencent-rpc-enhancement/pom.xml b/spring-cloud-tencent-rpc-enhancement/pom.xml index b4560f07..5d7cf2d2 100644 --- a/spring-cloud-tencent-rpc-enhancement/pom.xml +++ b/spring-cloud-tencent-rpc-enhancement/pom.xml @@ -50,6 +50,18 @@ true + + org.springframework.cloud + spring-cloud-gateway-server + true + + + + org.springframework.boot + spring-boot-starter-webflux + true + + com.tencent.polaris polaris-test-common 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 599b2cc7..ad47fde2 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 53bd5f41..e8196a64 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,44 @@ 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 b46f1435..487c6413 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 c7e97404..7d6a94b0 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 54a389c8..eaa0ae91 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 0ec539eb..03ff2c46 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; @@ -68,10 +71,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; } @@ -153,7 +158,11 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter if (apply(httpStatus)) { 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)) { String label = labels.get(0); @@ -163,7 +172,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 . @@ -176,12 +185,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); 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 00000000..33b2b556 --- /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 00000000..630feb34 --- /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 00000000..a251b67a --- /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 00000000..e3309a98 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporter.java @@ -0,0 +1,145 @@ +/* + * 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.http.HttpStatus; +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(HttpStatus.valueOf(response.statusCode().value()))) { + 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 4c9c8a4e..351c278c 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 199fd36b..e768dd91 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 111c031c..e3483dec 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 76027f6d..e1c8eacf 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 9e926703..dd1ac56e 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; @@ -53,6 +63,7 @@ import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) public class ExceptionPolarisReporterTest { + private static MockedStatic mockedApplicationContextAwareUtils; private static MockedStatic mockedReporterUtils; @Mock private ConsumerAPI consumerAPI; @@ -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,32 @@ 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 409ac5f8..75312774 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,18 @@ 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 b82eac46..04c3726c 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; @@ -53,6 +63,7 @@ import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) public class SuccessPolarisReporterTest { + private static MockedStatic mockedApplicationContextAwareUtils; private static MockedStatic mockedReporterUtils; @Mock private ConsumerAPI consumerAPI; @@ -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 00000000..84056bf6 --- /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 00000000..f8c8b771 --- /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 7074a39b..86c0c74a 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 4ef67859..fbfdcc9f 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 00000000..37698c9b --- /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(); + } + +}