diff --git a/CHANGELOG.md b/CHANGELOG.md index 58c9610b0..eb67ad2ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,4 +2,5 @@ --- - [feature: optimize polaris-discovery-example/discovery-callee-service, add client-ip return.](https://github.com/Tencent/spring-cloud-tencent/pull/941) +- [feat:support webclient and gateway report call metrics](https://github.com/Tencent/spring-cloud-tencent/pull/942) - [docs:prevent the release of the final version of the sdk.](https://github.com/Tencent/spring-cloud-tencent/pull/945) diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreaker.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreaker.java index d1d70b9e3..3a6914940 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreaker.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreaker.java @@ -22,6 +22,8 @@ import java.util.function.Supplier; import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder; import com.tencent.cloud.polaris.circuitbreaker.common.PolarisResultToErrorCode; +import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils; +import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import com.tencent.polaris.circuitbreak.api.FunctionalDecorator; @@ -43,10 +45,18 @@ public class PolarisCircuitBreaker implements CircuitBreaker { private final FunctionalDecorator decorator; - public PolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, CircuitBreakAPI circuitBreakAPI) { + private final PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf; + + private final ConsumerAPI consumerAPI; + + public PolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, + ConsumerAPI consumerAPI, + CircuitBreakAPI circuitBreakAPI) { FunctionalDecoratorRequest makeDecoratorRequest = new FunctionalDecoratorRequest(new ServiceKey(conf.getNamespace(), conf.getService()), conf.getMethod()); makeDecoratorRequest.setSourceService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService())); makeDecoratorRequest.setResultToErrorCode(new PolarisResultToErrorCode()); + this.consumerAPI = consumerAPI; + this.conf = conf; this.decorator = circuitBreakAPI.makeFunctionalDecorator(makeDecoratorRequest); } @@ -58,6 +68,7 @@ public class PolarisCircuitBreaker implements CircuitBreaker { } catch (CallAbortedException e) { LOGGER.debug("PolarisCircuitBreaker CallAbortedException: {}", e.getMessage()); + PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, e); return fallback.apply(e); } catch (Exception e) { diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerFactory.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerFactory.java index d80ae90e5..1c789befe 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerFactory.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerFactory.java @@ -21,6 +21,7 @@ import java.util.function.Function; import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder; import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils; +import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; @@ -47,15 +48,18 @@ public class PolarisCircuitBreakerFactory private final CircuitBreakAPI circuitBreakAPI; - public PolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI) { + private final ConsumerAPI consumerAPI; + + public PolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) { this.circuitBreakAPI = circuitBreakAPI; + this.consumerAPI = consumerAPI; } @Override public CircuitBreaker create(String id) { PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf = getConfigurations() .computeIfAbsent(id, defaultConfiguration); - return new PolarisCircuitBreaker(conf, circuitBreakAPI); + return new PolarisCircuitBreaker(conf, consumerAPI, circuitBreakAPI); } @Override diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreaker.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreaker.java index cfd7e481a..56b633ef4 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreaker.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreaker.java @@ -22,11 +22,14 @@ import java.util.function.Function; import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder; import com.tencent.cloud.polaris.circuitbreaker.common.PolarisResultToErrorCode; import com.tencent.cloud.polaris.circuitbreaker.reactor.PolarisCircuitBreakerReactorTransformer; +import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils; +import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import com.tencent.polaris.circuitbreak.api.InvokeHandler; import com.tencent.polaris.circuitbreak.api.pojo.FunctionalDecoratorRequest; import com.tencent.polaris.circuitbreak.api.pojo.InvokeContext; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -41,10 +44,18 @@ public class ReactivePolarisCircuitBreaker implements ReactiveCircuitBreaker { private final InvokeHandler invokeHandler; - public ReactivePolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, CircuitBreakAPI circuitBreakAPI) { + private final ConsumerAPI consumerAPI; + + private final PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf; + + public ReactivePolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, + ConsumerAPI consumerAPI, + CircuitBreakAPI circuitBreakAPI) { InvokeContext.RequestContext requestContext = new FunctionalDecoratorRequest(new ServiceKey(conf.getNamespace(), conf.getService()), conf.getMethod()); requestContext.setSourceService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService())); requestContext.setResultToErrorCode(new PolarisResultToErrorCode()); + this.consumerAPI = consumerAPI; + this.conf = conf; this.invokeHandler = circuitBreakAPI.makeInvokeHandler(requestContext); } @@ -53,7 +64,12 @@ public class ReactivePolarisCircuitBreaker implements ReactiveCircuitBreaker { public Mono run(Mono toRun, Function> fallback) { Mono toReturn = toRun.transform(new PolarisCircuitBreakerReactorTransformer<>(invokeHandler)); if (fallback != null) { - toReturn = toReturn.onErrorResume(fallback); + toReturn = toReturn.onErrorResume(throwable -> { + if (throwable instanceof CallAbortedException) { + PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, (CallAbortedException) throwable); + } + return fallback.apply(throwable); + }); } return toReturn; } @@ -62,7 +78,12 @@ public class ReactivePolarisCircuitBreaker implements ReactiveCircuitBreaker { public Flux run(Flux toRun, Function> fallback) { Flux toReturn = toRun.transform(new PolarisCircuitBreakerReactorTransformer<>(invokeHandler)); if (fallback != null) { - toReturn = toReturn.onErrorResume(fallback); + toReturn = toReturn.onErrorResume(throwable -> { + if (throwable instanceof CallAbortedException) { + PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, (CallAbortedException) throwable); + } + return fallback.apply(throwable); + }); } return toReturn; } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreakerFactory.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreakerFactory.java index 4a481e866..0a7bbb01f 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,16 +47,18 @@ 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; } - @Override public ReactiveCircuitBreaker create(String id) { PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf = getConfigurations() .computeIfAbsent(id, defaultConfiguration); - return new ReactivePolarisCircuitBreaker(conf, circuitBreakAPI); + return new ReactivePolarisCircuitBreaker(conf, consumerAPI, circuitBreakAPI); } @Override diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java index b2792737a..b73303f78 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java @@ -25,6 +25,7 @@ import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModif import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerRestTemplateBeanPostProcessor; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory; import com.tencent.polaris.client.api.SDKContext; @@ -62,8 +63,8 @@ public class PolarisCircuitBreakerAutoConfiguration { @Bean @ConditionalOnMissingBean(CircuitBreakerFactory.class) - public CircuitBreakerFactory polarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI) { - PolarisCircuitBreakerFactory factory = new PolarisCircuitBreakerFactory(circuitBreakAPI); + public CircuitBreakerFactory polarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) { + PolarisCircuitBreakerFactory factory = new PolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI); customizers.forEach(customizer -> customizer.customize(factory)); return factory; } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/ReactivePolarisCircuitBreakerAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/ReactivePolarisCircuitBreakerAutoConfiguration.java index 9d9bf911e..fdb89b2c9 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/ReactivePolarisCircuitBreakerAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/ReactivePolarisCircuitBreakerAutoConfiguration.java @@ -24,6 +24,7 @@ import com.tencent.cloud.polaris.circuitbreaker.ReactivePolarisCircuitBreakerFac import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory; import com.tencent.polaris.client.api.SDKContext; @@ -60,8 +61,8 @@ public class ReactivePolarisCircuitBreakerAutoConfiguration { @Bean @ConditionalOnMissingBean(ReactiveCircuitBreakerFactory.class) - public ReactiveCircuitBreakerFactory polarisReactiveCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI) { - ReactivePolarisCircuitBreakerFactory factory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI); + public ReactiveCircuitBreakerFactory polarisReactiveCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) { + ReactivePolarisCircuitBreakerFactory factory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI); customizers.forEach(customizer -> customizer.customize(factory)); return factory; } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtils.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtils.java index 885e2917b..961c30802 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,25 @@ 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 a78b59aff..f4bd9ede1 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerIntegrationTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerIntegrationTest.java @@ -39,6 +39,7 @@ import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory; +import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.client.util.Utils; import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto; import com.tencent.polaris.test.common.TestUtils; @@ -175,9 +176,9 @@ public class PolarisCircuitBreakerIntegrationTest { @Bean @PolarisCircuitBreaker(fallback = "fallback") - public RestTemplate defaultRestTemplate(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { + public RestTemplate defaultRestTemplate(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { RestTemplate defaultRestTemplate = new RestTemplate(); - EnhancedRestTemplateReporter enhancedRestTemplateReporter = new EnhancedRestTemplateReporter(properties, consumerAPI); + EnhancedRestTemplateReporter enhancedRestTemplateReporter = new EnhancedRestTemplateReporter(properties, context, consumerAPI); defaultRestTemplate.setErrorHandler(enhancedRestTemplateReporter); return defaultRestTemplate; } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerMockServerTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerMockServerTest.java index 36976da74..5058a8a22 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/util/PolarisCircuitBreakerUtilsTests.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtilsTests.java new file mode 100644 index 000000000..e6213dbab --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtilsTests.java @@ -0,0 +1,71 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.circuitbreaker.util; + +import java.util.HashMap; + +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.pojo.CircuitBreakerStatus; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.mockito.ArgumentMatchers.anyString; + +public class PolarisCircuitBreakerUtilsTests { + + private static MockedStatic mockedApplicationContextAwareUtils; + + + @BeforeAll + static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); + } + + @AfterAll + static void afterAll() { + mockedApplicationContextAwareUtils.close(); + } + + @BeforeEach + void setUp() { + MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST; + MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER; + } + + @Test + public void testReportStatus() { + ConsumerAPI consumerAPI = Mockito.mock(ConsumerAPI.class); + Mockito.doNothing().when(consumerAPI).updateServiceCallResult(new ServiceCallResult()); + PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf = new PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration(); + PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, new CallAbortedException("mock", new CircuitBreakerStatus.FallbackInfo(0, new HashMap<>(), ""))); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerClientConfiguration.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerClientConfiguration.java index 3d9bf1a40..cce8b0a9f 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerClientConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerClientConfiguration.java @@ -18,6 +18,10 @@ package com.tencent.cloud.polaris.loadbalancer; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; +import com.tencent.cloud.polaris.loadbalancer.reactive.PolarisLoadBalancerClientRequestTransformer; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.factory.api.DiscoveryAPIFactory; import com.tencent.polaris.router.api.core.RouterAPI; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -29,6 +33,7 @@ import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerClientRequestTransformer; import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; @@ -65,6 +70,13 @@ public class PolarisLoadBalancerClientConfiguration { loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), routerAPI); } + @Bean + @ConditionalOnMissingBean + public LoadBalancerClientRequestTransformer polarisLoadBalancerClientRequestTransformer(SDKContext sdkContext) { + ConsumerAPI consumerAPI = DiscoveryAPIFactory.createConsumerAPIByContext(sdkContext); + return new PolarisLoadBalancerClientRequestTransformer(consumerAPI); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnReactiveDiscoveryEnabled @Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER) diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/reactive/PolarisLoadBalancerClientRequestTransformer.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/reactive/PolarisLoadBalancerClientRequestTransformer.java new file mode 100644 index 000000000..cee59298b --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/reactive/PolarisLoadBalancerClientRequestTransformer.java @@ -0,0 +1,44 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.loadbalancer.reactive; + +import com.tencent.cloud.common.constant.HeaderConstant; +import com.tencent.polaris.api.core.ConsumerAPI; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerClientRequestTransformer; +import org.springframework.http.HttpHeaders; +import org.springframework.web.reactive.function.client.ClientRequest; + +public class PolarisLoadBalancerClientRequestTransformer implements LoadBalancerClientRequestTransformer { + + private final ConsumerAPI consumerAPI; + + public PolarisLoadBalancerClientRequestTransformer(ConsumerAPI consumerAPI) { + this.consumerAPI = consumerAPI; + } + + @Override + public ClientRequest transformRequest(ClientRequest request, ServiceInstance instance) { + if (instance != null) { + HttpHeaders headers = request.headers(); + headers.add(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, instance.getServiceId()); + } + return request; + } +} diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java index e5cc018d6..10ccfb2ae 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java @@ -25,6 +25,7 @@ import java.util.Set; import javax.annotation.PostConstruct; +import com.tencent.cloud.common.constant.HeaderConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; @@ -32,6 +33,7 @@ import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentReactiv import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils; import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils; +import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.rpc.Argument; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; @@ -116,6 +118,10 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { dataBuffer = response.bufferFactory().allocateBuffer() .write(rejectTips.getBytes(StandardCharsets.UTF_8)); } + response.getHeaders().add(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc()); + if (Objects.nonNull(quotaResponse.getActiveRule())) { + response.getHeaders().add(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, quotaResponse.getActiveRule().getName().getValue()); + } return response.writeWith(Mono.just(dataBuffer)); } // Unirate diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java index 1bc3a5203..027ae91f2 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java @@ -28,6 +28,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import com.tencent.cloud.common.constant.HeaderConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; @@ -35,6 +36,7 @@ import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServlet import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils; import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils; +import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.rpc.Argument; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; @@ -111,6 +113,10 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter { response.setContentType("text/html;charset=UTF-8"); response.getWriter().write(rejectTips); } + response.addHeader(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc()); + if (Objects.nonNull(quotaResponse.getActiveRule())) { + response.addHeader(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, quotaResponse.getActiveRule().getName().getValue()); + } return; } // Unirate diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java index 00c61a040..dac993921 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java @@ -94,10 +94,12 @@ public class QuotaCheckReactiveFilterTest { return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk")); } else if (serviceName.equals("TestApp3")) { - return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited")); + QuotaResponse response = new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited")); + response.setActiveRule(RateLimitProto.Rule.newBuilder().build()); + return response; } else { - return new QuotaResponse(new QuotaResult(null, 0, null)); + return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, null)); } }); diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilterTest.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilterTest.java index a632c9c17..13065c92b 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilterTest.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilterTest.java @@ -94,7 +94,9 @@ public class QuotaCheckServletFilterTest { return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk")); } else if (serviceName.equals("TestApp3")) { - return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited")); + QuotaResponse response = new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited")); + response.setActiveRule(RateLimitProto.Rule.newBuilder().build()); + return response; } else { return new QuotaResponse(new QuotaResult(null, 0, null)); diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/HeaderConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/HeaderConstant.java new file mode 100644 index 000000000..feffd0f49 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/HeaderConstant.java @@ -0,0 +1,52 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.common.constant; + +/** + * Built-in system http header fields. + */ +public final class HeaderConstant { + + /** + * The called service returns the real call result of its own processing request. + */ + public static final String INTERNAL_CALLEE_RET_STATUS = "internal-callee-retstatus"; + + /** + * The name of the rule that the current limit/circiutbreaker rule takes effect. + */ + public static final String INTERNAL_ACTIVE_RULE_NAME = "internal-callee-activerule"; + + /** + * The name information of the called service. + */ + public static final String INTERNAL_CALLEE_SERVICE_ID = "internal-callee-serviceid"; + + /** + * The name information of the called instance host. + */ + public static final String INTERNAL_CALLEE_INSTANCE_HOST = "internal-callee-instance-host"; + + /** + * The name information of the called instance port. + */ + public static final String INTERNAL_CALLEE_INSTANCE_PORT = "internal-callee-instance-port"; + + private HeaderConstant() { + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/RequestLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/RequestLabelUtils.java new file mode 100644 index 000000000..d76e5bf4d --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/RequestLabelUtils.java @@ -0,0 +1,34 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.common.util; + +/** + * Request Label Utils. + */ +public final class RequestLabelUtils { + + private RequestLabelUtils() { + } + + public static String convertLabel(String label) { + label = label.replaceAll("\"|\\{|\\}", "") + .replaceAll(",", "|"); + return label; + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/pom.xml b/spring-cloud-tencent-rpc-enhancement/pom.xml index b4560f070..5d7cf2d24 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 599b2cc76..ad47fde2b 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java @@ -19,13 +19,18 @@ package com.tencent.cloud.rpc.enhancement; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Objects; +import com.tencent.cloud.common.constant.HeaderConstant; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.pojo.RetStatus; +import com.tencent.polaris.api.utils.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; @@ -116,4 +121,31 @@ public abstract class AbstractPolarisReporterAdapter { // DEFAULT RETURN FALSE. return false; } + + protected RetStatus getRetStatusFromRequest(HttpHeaders headers, RetStatus defaultVal) { + if (headers.containsKey(HeaderConstant.INTERNAL_CALLEE_RET_STATUS)) { + List values = headers.get(HeaderConstant.INTERNAL_CALLEE_RET_STATUS); + if (CollectionUtils.isNotEmpty(values)) { + String retStatusVal = com.tencent.polaris.api.utils.StringUtils.defaultString(values.get(0)); + if (Objects.equals(retStatusVal, RetStatus.RetFlowControl.getDesc())) { + return RetStatus.RetFlowControl; + } + if (Objects.equals(retStatusVal, RetStatus.RetReject.getDesc())) { + return RetStatus.RetReject; + } + } + } + return defaultVal; + } + + protected String getActiveRuleNameFromRequest(HttpHeaders headers) { + if (headers.containsKey(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME)) { + Collection values = headers.get(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME); + if (CollectionUtils.isNotEmpty(values)) { + String val = com.tencent.polaris.api.utils.StringUtils.defaultString(new ArrayList<>(values).get(0)); + return val; + } + } + return ""; + } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java index 53bd5f414..c26d1269b 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java @@ -30,7 +30,11 @@ import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.ExceptionPolarisR import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.SuccessPolarisReporter; import com.tencent.cloud.rpc.enhancement.resttemplate.BlockingLoadBalancerClientAspect; import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter; +import com.tencent.cloud.rpc.enhancement.scg.EnhancedPolarisHttpClientCustomizer; +import com.tencent.cloud.rpc.enhancement.scg.EnhancedPolarisHttpHeadersFilter; +import com.tencent.cloud.rpc.enhancement.webclient.EnhancedWebClientReporter; import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; @@ -42,11 +46,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.gateway.config.HttpClientCustomizer; +import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Role; import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; /** * Auto Configuration for Polaris {@link feign.Feign} OR {@link RestTemplate} which can automatically bring in the call @@ -89,14 +96,16 @@ public class RpcEnhancementAutoConfiguration { @Bean public SuccessPolarisReporter successPolarisReporter(RpcEnhancementReporterProperties properties, - @Autowired(required = false) ConsumerAPI consumerAPI) { - return new SuccessPolarisReporter(properties, consumerAPI); + @Autowired(required = false) SDKContext context, + @Autowired(required = false) ConsumerAPI consumerAPI) { + return new SuccessPolarisReporter(properties, context, consumerAPI); } @Bean public ExceptionPolarisReporter exceptionPolarisReporter(RpcEnhancementReporterProperties properties, - @Autowired(required = false) ConsumerAPI consumerAPI) { - return new ExceptionPolarisReporter(properties, consumerAPI); + @Autowired(required = false) SDKContext context, + @Autowired(required = false) ConsumerAPI consumerAPI) { + return new ExceptionPolarisReporter(properties, context, consumerAPI); } } } @@ -117,8 +126,8 @@ public class RpcEnhancementAutoConfiguration { @Bean public EnhancedRestTemplateReporter enhancedRestTemplateReporter( - RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { - return new EnhancedRestTemplateReporter(properties, consumerAPI); + RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { + return new EnhancedRestTemplateReporter(properties, context, consumerAPI); } @Bean @@ -137,4 +146,43 @@ public class RpcEnhancementAutoConfiguration { return new BlockingLoadBalancerClientAspect(); } } + + /** + * Configuration for Polaris {@link org.springframework.web.reactive.function.client.WebClient} which can automatically bring in the call + * results for reporting. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "org.springframework.web.reactive.function.client.WebClient") + protected static class PolarisWebClientAutoConfiguration { + + @Bean + public ExchangeFilterFunction exchangeFilterFunction( + RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { + return new EnhancedWebClientReporter(properties, context, consumerAPI); + } + } + + /** + * Configuration for Polaris {@link org.springframework.web.reactive.function.client.WebClient} which can automatically bring in the call + * results for reporting. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "org.springframework.cloud.gateway.config.GatewayAutoConfiguration") + @Role(RootBeanDefinition.ROLE_INFRASTRUCTURE) + protected static class PolarisGatewayAutoConfiguration { + + @Bean + @ConditionalOnClass(name = {"org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter"}) + public HttpHeadersFilter enhancedPolarisHttpHeadersFilter() { + return new EnhancedPolarisHttpHeadersFilter(); + } + + @Bean + @ConditionalOnClass(name = {"org.springframework.cloud.gateway.config.HttpClientCustomizer"}) + public HttpClientCustomizer httpClientCustomizer( + RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { + return new EnhancedPolarisHttpClientCustomizer(properties, context, consumerAPI); + } + + } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporter.java index b46f1435c..487c64134 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporter.java @@ -18,7 +18,9 @@ package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter; import java.net.SocketTimeoutException; +import java.util.ArrayList; +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin; @@ -26,28 +28,36 @@ import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; import feign.Request; import feign.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; +import org.springframework.http.HttpHeaders; /** * Polaris reporter when feign call fails. * * @author Haotian Zhang */ -public class ExceptionPolarisReporter implements EnhancedFeignPlugin { +public class ExceptionPolarisReporter extends AbstractPolarisReporterAdapter implements EnhancedFeignPlugin { private static final Logger LOG = LoggerFactory.getLogger(ExceptionPolarisReporter.class); private final RpcEnhancementReporterProperties reporterProperties; private final ConsumerAPI consumerAPI; + private final SDKContext context; + + public ExceptionPolarisReporter(RpcEnhancementReporterProperties reporterProperties, + SDKContext context, ConsumerAPI consumerAPI) { + super(reporterProperties); this.reporterProperties = reporterProperties; + this.context = context; this.consumerAPI = consumerAPI; } @@ -78,7 +88,13 @@ public class ExceptionPolarisReporter implements EnhancedFeignPlugin { } LOG.debug("Will report result of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.", retStatus.name(), request.httpMethod() .name(), request.url(), response.status(), delay); - ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(request, response, delay, retStatus); + ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(this.context, request, response, + delay, retStatus, serviceCallResult -> { + HttpHeaders headers = new HttpHeaders(); + response.headers().forEach((s, strings) -> headers.addAll(s, new ArrayList<>(strings))); + serviceCallResult.setRetStatus(getRetStatusFromRequest(headers, serviceCallResult.getRetStatus())); + serviceCallResult.setRuleName(getActiveRuleNameFromRequest(headers)); + }); consumerAPI.updateServiceCallResult(resultRequest); } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtils.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtils.java index c7e97404a..b53c6a49e 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,10 +87,13 @@ 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; } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java index 54a389c8c..eaa0ae916 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java @@ -17,6 +17,8 @@ package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter; +import java.util.ArrayList; + import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; @@ -25,12 +27,14 @@ import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; import feign.Request; import feign.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; /** @@ -44,8 +48,11 @@ public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter imple private final ConsumerAPI consumerAPI; - public SuccessPolarisReporter(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { + private final SDKContext context; + + public SuccessPolarisReporter(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { super(properties); + this.context = context; this.consumerAPI = consumerAPI; } @@ -75,7 +82,13 @@ public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter imple } LOG.debug("Will report result of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.", retStatus.name(), request.httpMethod() .name(), request.url(), response.status(), delay); - ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(request, response, delay, retStatus); + ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(this.context, request, response, + delay, retStatus, serviceCallResult -> { + HttpHeaders headers = new HttpHeaders(); + response.headers().forEach((s, strings) -> headers.addAll(s, new ArrayList<>(strings))); + serviceCallResult.setRetStatus(getRetStatusFromRequest(headers, serviceCallResult.getRetStatus())); + serviceCallResult.setRuleName(getActiveRuleNameFromRequest(headers)); + }); consumerAPI.updateServiceCallResult(resultRequest); } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java index 2c3ea1fe2..a5e754a7e 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java @@ -23,10 +23,12 @@ import java.net.URI; import java.net.URLDecoder; import java.util.List; import java.util.Map; +import java.util.Objects; import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.util.RequestLabelUtils; import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; import com.tencent.polaris.api.core.ConsumerAPI; @@ -34,6 +36,7 @@ import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.api.rpc.ServiceCallResult; import com.tencent.polaris.api.utils.CollectionUtils; +import com.tencent.polaris.client.api.SDKContext; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,10 +69,12 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter public static final String HEADER_HAS_ERROR = "X-SCT-Has-Error"; private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedRestTemplateReporter.class); private final ConsumerAPI consumerAPI; + private final SDKContext context; private ResponseErrorHandler delegateHandler; - public EnhancedRestTemplateReporter(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { + public EnhancedRestTemplateReporter(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { super(properties); + this.context = context; this.consumerAPI = consumerAPI; } @@ -149,7 +154,11 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter if (apply(response.getStatusCode())) { resultRequest.setRetStatus(RetStatus.RetFail); } - + resultRequest.setRetStatus(getRetStatusFromRequest(response.getHeaders(), resultRequest.getRetStatus())); + resultRequest.setRuleName(getActiveRuleNameFromRequest(response.getHeaders())); + if (Objects.nonNull(context)) { + resultRequest.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP()); + } List labels = response.getHeaders().get(RouterConstant.ROUTER_LABEL_HEADER); if (CollectionUtils.isNotEmpty(labels)) { String label = labels.get(0); @@ -159,7 +168,7 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter catch (UnsupportedEncodingException e) { LOGGER.error("unsupported charset exception " + UTF_8, e); } - resultRequest.setLabels(convertLabel(label)); + resultRequest.setLabels(RequestLabelUtils.convertLabel(label)); } // processing report with consumerAPI . @@ -172,12 +181,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 000000000..33b2b556c --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClient.java @@ -0,0 +1,173 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.rpc.enhancement.scg; + +import java.net.SocketTimeoutException; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; + +import com.tencent.cloud.common.constant.HeaderConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.pojo.RetStatus; +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; +import io.netty.handler.codec.http.HttpHeaders; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.netty.http.client.HttpClient; +import reactor.netty.http.client.HttpClientConfig; +import reactor.netty.http.client.HttpClientResponse; + +import org.springframework.http.HttpStatus; + +public class EnhancedPolarisHttpClient extends HttpClient { + + private static final Logger LOG = LoggerFactory.getLogger(EnhancedPolarisHttpClient.class); + + private final RpcEnhancementReporterProperties properties; + private final SDKContext context; + private final ConsumerAPI consumerAPI; + private final Reporter adapter; + private final BiConsumer handler = new BiConsumer() { + @Override + public void accept(HttpClientResponse httpClientResponse, Throwable throwable) { + if (Objects.isNull(consumerAPI)) { + return; + } + HttpHeaders responseHeaders = httpClientResponse.responseHeaders(); + + ServiceCallResult result = new ServiceCallResult(); + result.setCallerService(new ServiceKey(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE)); + result.setNamespace(MetadataContext.LOCAL_NAMESPACE); + + Map metadata = MetadataContextHolder.get().getLoadbalancerMetadata(); + result.setDelay(System.currentTimeMillis() - Long.parseLong(metadata.get("startTime"))); + result.setService(metadata.get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID)); + result.setHost(metadata.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST)); + result.setPort(Integer.parseInt(metadata.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT))); + RetStatus status = RetStatus.RetSuccess; + if (Objects.isNull(throwable)) { + if (EnhancedPolarisHttpClient.this.adapter.apply(HttpStatus.valueOf(httpClientResponse.status() + .code()))) { + status = RetStatus.RetFail; + } + org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders(); + responseHeaders.forEach(entry -> headers.add(entry.getKey(), entry.getValue())); + status = adapter.getRetStatusFromRequest(headers, status); + result.setRuleName(adapter.getActiveRuleNameFromRequest(headers)); + } + else { + if (throwable instanceof SocketTimeoutException) { + status = RetStatus.RetTimeout; + } + } + result.setMethod(httpClientResponse.uri()); + result.setRetCode(httpClientResponse.status().code()); + result.setRetStatus(status); + if (Objects.nonNull(context)) { + result.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP()); + } + try { + consumerAPI.updateServiceCallResult(result); + } + catch (Throwable ex) { + LOG.error("update service call result fail", ex); + } + } + }; + private HttpClient target; + + public EnhancedPolarisHttpClient( + HttpClient client, + RpcEnhancementReporterProperties properties, + SDKContext context, + ConsumerAPI consumerAPI) { + this.properties = properties; + this.context = context; + this.consumerAPI = consumerAPI; + this.target = client; + this.adapter = new Reporter(properties); + this.registerReportHandler(); + } + + @Override + public HttpClientConfig configuration() { + return target.configuration(); + } + + @Override + protected HttpClient duplicate() { + return new EnhancedPolarisHttpClient(target, properties, context, consumerAPI); + } + + private void registerReportHandler() { + target = target.doOnRequest((request, connection) -> { + String serviceId = request.requestHeaders().get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID); + String host = request.requestHeaders().get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST); + String port = request.requestHeaders().get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT); + if (StringUtils.isNotBlank(serviceId)) { + MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, serviceId); + MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST, host); + MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT, port); + MetadataContextHolder.get().setLoadbalancer("startTime", System.currentTimeMillis() + ""); + } + + request.requestHeaders().remove(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID); + request.requestHeaders().remove(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST); + request.requestHeaders().remove(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT); + }); + target = target.doOnResponse((httpClientResponse, connection) -> handler.accept(httpClientResponse, null)); + target = target.doOnResponseError(handler); + } + + + private static class Reporter extends AbstractPolarisReporterAdapter { + + /** + * Constructor With {@link RpcEnhancementReporterProperties} . + * + * @param reportProperties instance of {@link RpcEnhancementReporterProperties}. + */ + protected Reporter(RpcEnhancementReporterProperties reportProperties) { + super(reportProperties); + } + + @Override + public boolean apply(HttpStatus httpStatus) { + return super.apply(httpStatus); + } + + @Override + public RetStatus getRetStatusFromRequest(org.springframework.http.HttpHeaders headers, RetStatus defaultVal) { + return super.getRetStatusFromRequest(headers, defaultVal); + } + + @Override + public String getActiveRuleNameFromRequest(org.springframework.http.HttpHeaders headers) { + return super.getActiveRuleNameFromRequest(headers); + } + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizer.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizer.java new file mode 100644 index 000000000..630feb349 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizer.java @@ -0,0 +1,43 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.rpc.enhancement.scg; + +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; +import reactor.netty.http.client.HttpClient; + +import org.springframework.cloud.gateway.config.HttpClientCustomizer; + +public class EnhancedPolarisHttpClientCustomizer implements HttpClientCustomizer { + + private final RpcEnhancementReporterProperties properties; + private final SDKContext context; + private final ConsumerAPI consumerAPI; + + public EnhancedPolarisHttpClientCustomizer(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { + this.properties = properties; + this.context = context; + this.consumerAPI = consumerAPI; + } + + @Override + public HttpClient customize(HttpClient httpClient) { + return new EnhancedPolarisHttpClient(httpClient, properties, context, consumerAPI); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilter.java new file mode 100644 index 000000000..a251b67a1 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilter.java @@ -0,0 +1,71 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.rpc.enhancement.scg; + +import java.util.List; + +import com.tencent.cloud.common.constant.HeaderConstant; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; +import org.springframework.http.HttpHeaders; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR; + +public class EnhancedPolarisHttpHeadersFilter implements HttpHeadersFilter { + + public EnhancedPolarisHttpHeadersFilter() { + } + + @Override + public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) { + Response serviceInstanceResponse = exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR); + if (serviceInstanceResponse == null || !serviceInstanceResponse.hasServer()) { + return input; + } + ServiceInstance instance = serviceInstanceResponse.getServer(); + write(input, HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, instance.getServiceId(), true); + write(input, HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST, instance.getHost(), true); + write(input, HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT, instance.getPort() + "", true); + return input; + } + + @Override + public boolean supports(Type type) { + return Type.REQUEST.equals(type); + } + + private void write(HttpHeaders headers, String name, String value, boolean append) { + if (value == null) { + return; + } + if (append) { + headers.add(name, value); + // these headers should be treated as a single comma separated header + List values = headers.get(name); + String delimitedValue = StringUtils.collectionToCommaDelimitedString(values); + headers.set(name, delimitedValue); + } + else { + headers.set(name, value); + } + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporter.java new file mode 100644 index 000000000..33737cdd7 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporter.java @@ -0,0 +1,144 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.rpc.enhancement.webclient; + +import java.io.UnsupportedEncodingException; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URLDecoder; +import java.util.Collection; +import java.util.Objects; + +import com.tencent.cloud.common.constant.HeaderConstant; +import com.tencent.cloud.common.constant.RouterConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.RequestLabelUtils; +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.pojo.RetStatus; +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.api.utils.CollectionUtils; +import com.tencent.polaris.client.api.SDKContext; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +import org.springframework.http.HttpHeaders; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeFunction; + +import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; + +public class EnhancedWebClientReporter extends AbstractPolarisReporterAdapter implements ExchangeFilterFunction { + + protected static final String METRICS_WEBCLIENT_START_TIME = EnhancedWebClientReporter.class.getName() + + ".START_TIME"; + private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedWebClientReporter.class); + private final ConsumerAPI consumerAPI; + + private final SDKContext context; + + public EnhancedWebClientReporter(RpcEnhancementReporterProperties reportProperties, SDKContext context, ConsumerAPI consumerAPI) { + super(reportProperties); + this.context = context; + this.consumerAPI = consumerAPI; + } + + @Override + public Mono filter(ClientRequest request, ExchangeFunction next) { + return next.exchange(request).as((responseMono) -> instrumentResponse(request, responseMono)) + .contextWrite(this::putStartTime); + } + + Mono instrumentResponse(ClientRequest request, Mono responseMono) { + return Mono.deferContextual((ctx) -> responseMono.doOnEach((signal) -> { + // report result to polaris + if (!reportProperties.isEnabled()) { + return; + } + ServiceCallResult callResult = new ServiceCallResult(); + Long startTime = getStartTime(ctx); + callResult.setDelay(System.currentTimeMillis() - startTime); + + callResult.setNamespace(MetadataContext.LOCAL_NAMESPACE); + callResult.setService(request.headers().getFirst(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID)); + String sourceNamespace = MetadataContext.LOCAL_NAMESPACE; + String sourceService = MetadataContext.LOCAL_SERVICE; + if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) { + callResult.setCallerService(new ServiceKey(sourceNamespace, sourceService)); + } + + Collection labels = request.headers().get(RouterConstant.ROUTER_LABEL_HEADER); + if (CollectionUtils.isNotEmpty(labels) && labels.iterator().hasNext()) { + String label = labels.iterator().next(); + try { + label = URLDecoder.decode(label, UTF_8); + } + catch (UnsupportedEncodingException e) { + LOGGER.error("unsupported charset exception " + UTF_8, e); + } + callResult.setLabels(RequestLabelUtils.convertLabel(label)); + } + + URI uri = request.url(); + callResult.setMethod(uri.getPath()); + callResult.setHost(uri.getHost()); + // -1 means access directly by url, and use http default port number 80 + callResult.setPort(uri.getPort() == -1 ? 80 : uri.getPort()); + if (Objects.nonNull(context)) { + callResult.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP()); + } + + RetStatus retStatus = RetStatus.RetSuccess; + ClientResponse response = signal.get(); + if (Objects.nonNull(response)) { + HttpHeaders headers = response.headers().asHttpHeaders(); + + callResult.setRuleName(getActiveRuleNameFromRequest(headers)); + if (apply(response.statusCode())) { + retStatus = RetStatus.RetFail; + } + retStatus = getRetStatusFromRequest(headers, retStatus); + } + if (signal.isOnError()) { + Throwable throwable = signal.getThrowable(); + if (throwable instanceof SocketTimeoutException) { + retStatus = RetStatus.RetTimeout; + } + } + callResult.setRetStatus(retStatus); + + consumerAPI.updateServiceCallResult(callResult); + })); + } + + private Long getStartTime(ContextView context) { + return context.get(METRICS_WEBCLIENT_START_TIME); + } + + private Context putStartTime(Context context) { + return context.put(METRICS_WEBCLIENT_START_TIME, System.currentTimeMillis()); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapterTest.java index 4c9c8a4e9..b84d23d1f 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,49 @@ public class AbstractPolarisReporterAdapterTest { Assertions.assertThat(adapter.apply(HttpStatus.FORBIDDEN)).isEqualTo(true); } + + @Test + public void testGetRetStatusFromRequest() { + RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties(); + // Mock Condition + properties.getStatuses().clear(); + properties.getSeries().clear(); + properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR); + + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + + HttpHeaders headers = new HttpHeaders(); + RetStatus ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail); + Assertions.assertThat(ret).isEqualTo(RetStatus.RetFail); + + headers.set(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc()); + ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail); + Assertions.assertThat(ret).isEqualTo(RetStatus.RetFlowControl); + + headers.set(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetReject.getDesc()); + ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail); + Assertions.assertThat(ret).isEqualTo(RetStatus.RetReject); + } + + @Test + public void testGetActiveRuleNameFromRequest() { + RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties(); + // Mock Condition + properties.getStatuses().clear(); + properties.getSeries().clear(); + properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR); + + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + + HttpHeaders headers = new HttpHeaders(); + String ruleName = adapter.getActiveRuleNameFromRequest(headers); + Assertions.assertThat(ruleName).isEqualTo(""); + + headers.set(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, "mock_rule"); + ruleName = adapter.getActiveRuleNameFromRequest(headers); + Assertions.assertThat(ruleName).isEqualTo("mock_rule"); + } + /** * Simple Polaris CircuitBreak Adapter Implements . */ diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfigurationTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfigurationTest.java index 199fd36b7..e768dd914 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfigurationTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfigurationTest.java @@ -50,7 +50,7 @@ public class RpcEnhancementAutoConfigurationTest { RpcEnhancementAutoConfiguration.class, PolarisRestTemplateAutoConfigurationTester.class, FeignLoadBalancerAutoConfiguration.class)) - .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true", "spring.application.name=test"); + .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true", "spring.application.name=test", "spring.cloud.gateway.enabled=false"); @Test public void testDefaultInitialization() { diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementReporterPropertiesTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementReporterPropertiesTest.java index 111c031c5..e3483decc 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementReporterPropertiesTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementReporterPropertiesTest.java @@ -38,7 +38,10 @@ import static org.springframework.http.HttpStatus.Series.SERVER_ERROR; * @author Haotian Zhang */ @ExtendWith(SpringExtension.class) -@SpringBootTest(classes = RpcEnhancementReporterPropertiesTest.TestApplication.class, properties = "spring.application.name=test") +@SpringBootTest(classes = RpcEnhancementReporterPropertiesTest.TestApplication.class, properties = { + "spring.application.name=test", + "spring.cloud.gateway.enabled=false" +}) @ActiveProfiles("test") public class RpcEnhancementReporterPropertiesTest { diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClientTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClientTest.java index 76027f6dc..e1c8eacf6 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClientTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClientTest.java @@ -51,7 +51,7 @@ import static org.mockito.Mockito.mock; */ @ExtendWith(SpringExtension.class) @SpringBootTest(classes = EnhancedFeignClientTest.TestApplication.class, - properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"}) + properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp", "spring.cloud.gateway.enabled=false"}) public class EnhancedFeignClientTest { @Test diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporterTest.java index 9e926703a..70bbd2227 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporterTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporterTest.java @@ -18,17 +18,24 @@ package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter; import java.util.HashMap; +import java.util.function.Consumer; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; import feign.Request; +import feign.RequestTemplate; import feign.Response; +import feign.Target; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -37,9 +44,12 @@ import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -54,6 +64,7 @@ import static org.mockito.Mockito.verify; public class ExceptionPolarisReporterTest { private static MockedStatic mockedReporterUtils; + private static MockedStatic mockedApplicationContextAwareUtils; @Mock private ConsumerAPI consumerAPI; @Mock @@ -63,16 +74,27 @@ public class ExceptionPolarisReporterTest { @BeforeAll static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class); - mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(Request.class), any(Response.class), anyLong(), any(RetStatus.class))) - .thenReturn(mock(ServiceCallResult.class)); + mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class), + any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class))) + .thenReturn(new ServiceCallResult()); } @AfterAll static void afterAll() { + mockedApplicationContextAwareUtils.close(); mockedReporterUtils.close(); } + @BeforeEach + void setUp() { + MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST; + MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER; + } + @Test public void testGetName() { assertThat(exceptionPolarisReporter.getName()).isEqualTo(ExceptionPolarisReporter.class.getName()); @@ -100,6 +122,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 409ac5f89..0ba60f5fe 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtilsTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtilsTest.java @@ -23,8 +23,12 @@ import java.net.URLEncoder; import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.polaris.api.config.Configuration; +import com.tencent.polaris.api.config.global.APIConfig; +import com.tencent.polaris.api.config.global.GlobalConfig; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; import feign.Request; import feign.RequestTemplate; import feign.Response; @@ -99,7 +103,9 @@ public class ReporterUtilsTest { Response response = mock(Response.class); doReturn(502).when(response).status(); - ServiceCallResult serviceCallResult = ReporterUtils.createServiceCallResult(request, response, 10L, RetStatus.RetSuccess); + ServiceCallResult serviceCallResult = ReporterUtils.createServiceCallResult(mockSDKContext(), request, response, 10L, RetStatus.RetSuccess, result -> { + + }); assertThat(serviceCallResult.getNamespace()).isEqualTo(NAMESPACE_TEST); assertThat(serviceCallResult.getService()).isEqualTo(SERVICE_PROVIDER); assertThat(serviceCallResult.getHost()).isEqualTo("1.1.1.1"); @@ -112,4 +118,17 @@ public class ReporterUtilsTest { assertThat(serviceCallResult.getRetCode()).isEqualTo(502); assertThat(serviceCallResult.getDelay()).isEqualTo(10L); } + + public static SDKContext mockSDKContext() { + APIConfig apiConfig = mock(APIConfig.class); + doReturn("127.0.0.1").when(apiConfig).getBindIP(); + GlobalConfig globalConfig = mock(GlobalConfig.class); + doReturn(apiConfig).when(globalConfig).getAPI(); + Configuration configuration = mock(Configuration.class); + doReturn(globalConfig).when(configuration).getGlobal(); + SDKContext context = mock(SDKContext.class); + doReturn(configuration).when(context).getConfig(); + + return context; + } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java index b82eac465..44b667b79 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java @@ -18,17 +18,24 @@ package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter; import java.util.HashMap; +import java.util.function.Consumer; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; import feign.Request; +import feign.RequestTemplate; import feign.Response; +import feign.Target; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -37,9 +44,12 @@ import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -54,6 +64,7 @@ import static org.mockito.Mockito.verify; public class SuccessPolarisReporterTest { private static MockedStatic mockedReporterUtils; + private static MockedStatic mockedApplicationContextAwareUtils; @Mock private ConsumerAPI consumerAPI; @Mock @@ -63,16 +74,27 @@ public class SuccessPolarisReporterTest { @BeforeAll static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class); - mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(Request.class), any(Response.class), anyLong(), any(RetStatus.class))) - .thenReturn(mock(ServiceCallResult.class)); + mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class), + any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class))) + .thenReturn(new ServiceCallResult()); } @AfterAll static void afterAll() { + mockedApplicationContextAwareUtils.close(); mockedReporterUtils.close(); } + @BeforeEach + void setUp() { + MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST; + MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER; + } + @Test public void testGetName() { assertThat(successPolarisReporter.getName()).isEqualTo(SuccessPolarisReporter.class.getName()); @@ -101,6 +123,32 @@ public class SuccessPolarisReporterTest { doReturn(true).when(reporterProperties).isEnabled(); successPolarisReporter.run(context); verify(context, times(1)).getRequest(); + + try { + mockedReporterUtils.close(); + // mock target + Target target = mock(Target.class); + doReturn(SERVICE_PROVIDER).when(target).name(); + + // mock RequestTemplate.class + RequestTemplate requestTemplate = new RequestTemplate(); + requestTemplate.feignTarget(target); + + EnhancedFeignContext feignContext = new EnhancedFeignContext(); + request = Request.create(Request.HttpMethod.GET, "/", new HashMap<>(), null, null, requestTemplate); + response = Response.builder() + .request(request) + .build(); + feignContext.setRequest(request); + feignContext.setResponse(response); + successPolarisReporter.run(feignContext); + } + finally { + mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class); + mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class), + any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class))) + .thenReturn(new ServiceCallResult()); + } } @Test diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizerTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizerTest.java new file mode 100644 index 000000000..84056bf69 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizerTest.java @@ -0,0 +1,49 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.rpc.enhancement.scg; + +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import reactor.netty.http.client.HttpClient; + +public class EnhancedPolarisHttpClientCustomizerTest { + + @Test + public void testCustomize() { + RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties(); + properties.setEnabled(true); + properties.getStatuses().clear(); + properties.getSeries().clear(); + + SDKContext context = Mockito.mock(SDKContext.class); + ConsumerAPI consumerAPI = Mockito.mock(ConsumerAPI.class); + + EnhancedPolarisHttpClientCustomizer clientCustomizer = new EnhancedPolarisHttpClientCustomizer(properties, context, consumerAPI); + + HttpClient client = HttpClient.create(); + HttpClient proxyClient = clientCustomizer.customize(client); + + Assertions.assertNotNull(proxyClient); + Assertions.assertEquals(EnhancedPolarisHttpClient.class.getName(), proxyClient.getClass().getName()); + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilterTest.java new file mode 100644 index 000000000..d8e14caa3 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilterTest.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.rpc.enhancement.scg; + +import java.util.Collections; + +import com.tencent.cloud.common.constant.HeaderConstant; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.DefaultResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.web.server.ServerWebExchange; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR; + +public class EnhancedPolarisHttpHeadersFilterTest { + + @Test + public void testFilter() { + EnhancedPolarisHttpHeadersFilter filter = new EnhancedPolarisHttpHeadersFilter(); + ServiceInstance instance = Mockito.mock(ServiceInstance.class); + Mockito.doReturn("mock_service").when(instance).getServiceId(); + Mockito.doReturn("127.0.0.1").when(instance).getHost(); + Mockito.doReturn(8080).when(instance).getPort(); + DefaultResponse response = new DefaultResponse(instance); + ServerWebExchange exchange = Mockito.mock(ServerWebExchange.class); + Mockito.doReturn(response).when(exchange).getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR); + HttpHeaders input = new HttpHeaders(); + HttpHeaders headers = filter.filter(input, exchange); + Assertions.assertEquals(Collections.singletonList("mock_service"), headers.get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID)); + Assertions.assertEquals(Collections.singletonList("127.0.0.1"), headers.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST)); + Assertions.assertEquals(Collections.singletonList("8080"), headers.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT)); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatPropertiesTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatPropertiesTest.java index 7074a39b6..86c0c74a7 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatPropertiesTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatPropertiesTest.java @@ -40,7 +40,8 @@ public class PolarisStatPropertiesTest { .withPropertyValues("spring.cloud.polaris.stat.path=/xxx") .withPropertyValues("spring.cloud.polaris.stat.pushgateway.enabled=true") .withPropertyValues("spring.cloud.polaris.stat.pushgateway.address=127.0.0.1:9091") - .withPropertyValues("spring.cloud.polaris.stat.pushgateway.push-interval=1000"); + .withPropertyValues("spring.cloud.polaris.stat.pushgateway.push-interval=1000") + .withPropertyValues("spring.cloud.gateway.enabled=false"); @Test public void testDefaultInitialization() { diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/StatConfigModifierTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/StatConfigModifierTest.java index 4ef678597..fbfdcc9f2 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/StatConfigModifierTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/config/StatConfigModifierTest.java @@ -42,7 +42,8 @@ public class StatConfigModifierTest { .withPropertyValues("spring.cloud.polaris.stat.host=127.0.0.1") .withPropertyValues("spring.cloud.polaris.stat.port=20000") .withPropertyValues("spring.cloud.polaris.stat.path=/xxx") - .withPropertyValues("spring.application.name=test"); + .withPropertyValues("spring.application.name=test") + .withPropertyValues("spring.cloud.gateway.enabled=false"); private final ApplicationContextRunner pushContextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(TestApplication.class)) @@ -51,13 +52,15 @@ public class StatConfigModifierTest { .withPropertyValues("spring.cloud.polaris.stat.pushgateway.enabled=true") .withPropertyValues("spring.cloud.polaris.stat.pushgateway.address=127.0.0.1:9091") .withPropertyValues("spring.cloud.polaris.stat.pushgateway.push-interval=1000") - .withPropertyValues("spring.application.name=test"); + .withPropertyValues("spring.application.name=test") + .withPropertyValues("spring.cloud.gateway.enabled=false"); private final ApplicationContextRunner disabledContextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(TestApplication.class)) .withPropertyValues("spring.cloud.polaris.enabled=true") .withPropertyValues("spring.cloud.polaris.stat.enabled=false") - .withPropertyValues("spring.application.name=test"); + .withPropertyValues("spring.application.name=test") + .withPropertyValues("spring.cloud.gateway.enabled=false"); @Test void testPull() { diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporterTest.java new file mode 100644 index 000000000..37698c9bc --- /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(); + } + +}