feat:support webclient and gateway report call metrics (#946)

pull/957/head
liaochuntao 1 year ago committed by GitHub
parent a6ff2f38b2
commit 73457a4e66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,3 +3,4 @@
- [feature: optimize polaris-discovery-example/discovery-callee-service, add client-ip return.](https://github.com/Tencent/spring-cloud-tencent/pull/940)
- [docs:prevent the release of the final version of the sdk.](https://github.com/Tencent/spring-cloud-tencent/pull/944)
- [feat:support webclient and gateway report call metrics](https://github.com/Tencent/spring-cloud-tencent/pull/946)

@ -22,6 +22,8 @@ import java.util.function.Supplier;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisResultToErrorCode;
import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.circuitbreak.api.FunctionalDecorator;
@ -43,10 +45,19 @@ public class PolarisCircuitBreaker implements CircuitBreaker {
private final FunctionalDecorator decorator;
public PolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, CircuitBreakAPI circuitBreakAPI) {
private final PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf;
private final ConsumerAPI consumerAPI;
public PolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf,
ConsumerAPI consumerAPI,
CircuitBreakAPI circuitBreakAPI) {
FunctionalDecoratorRequest makeDecoratorRequest = new FunctionalDecoratorRequest(new ServiceKey(conf.getNamespace(), conf.getService()), conf.getMethod());
makeDecoratorRequest.setSourceService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService()));
makeDecoratorRequest.setResultToErrorCode(new PolarisResultToErrorCode());
this.consumerAPI = consumerAPI;
this.conf = conf;
this.decorator = circuitBreakAPI.makeFunctionalDecorator(makeDecoratorRequest);
}
@ -58,6 +69,7 @@ public class PolarisCircuitBreaker implements CircuitBreaker {
}
catch (CallAbortedException e) {
LOGGER.debug("PolarisCircuitBreaker CallAbortedException: {}", e.getMessage());
PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, e);
return fallback.apply(e);
}
catch (Exception e) {

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

@ -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 <T> Mono<T> run(Mono<T> toRun, Function<Throwable, Mono<T>> fallback) {
Mono<T> 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 <T> Flux<T> run(Flux<T> toRun, Function<Throwable, Flux<T>> fallback) {
Flux<T> 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;
}

@ -21,6 +21,7 @@ import java.util.function.Function;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
@ -46,8 +47,11 @@ public class ReactivePolarisCircuitBreakerFactory extends
private final CircuitBreakAPI circuitBreakAPI;
public ReactivePolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI) {
private final ConsumerAPI consumerAPI;
public ReactivePolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) {
this.circuitBreakAPI = circuitBreakAPI;
this.consumerAPI = consumerAPI;
}
@ -55,9 +59,8 @@ public class ReactivePolarisCircuitBreakerFactory extends
public ReactiveCircuitBreaker create(String id) {
PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf = getConfigurations()
.computeIfAbsent(id, defaultConfiguration);
return new ReactivePolarisCircuitBreaker(conf, circuitBreakAPI);
return new ReactivePolarisCircuitBreaker(conf, consumerAPI, circuitBreakAPI);
}
@Override
protected PolarisCircuitBreakerConfigBuilder configBuilder(String id) {
String[] metadata = PolarisCircuitBreakerUtils.resolveCircuitBreakerId(id);

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

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

@ -17,7 +17,17 @@
package com.tencent.cloud.polaris.circuitbreaker.util;
import java.util.Objects;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
@ -28,6 +38,8 @@ import org.springframework.util.Assert;
*/
public final class PolarisCircuitBreakerUtils {
private static final Logger LOG = LoggerFactory.getLogger(PolarisCircuitBreakerUtils.class);
private PolarisCircuitBreakerUtils() {
}
@ -51,4 +63,24 @@ public final class PolarisCircuitBreakerUtils {
return new String[]{MetadataContext.LOCAL_NAMESPACE, id, ""};
}
public static void reportStatus(ConsumerAPI consumerAPI,
PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, CallAbortedException e) {
try {
ServiceCallResult result = new ServiceCallResult();
result.setMethod(conf.getMethod());
result.setNamespace(conf.getNamespace());
result.setService(conf.getService());
result.setRuleName(e.getRuleName());
result.setRetStatus(RetStatus.RetReject);
result.setCallerService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService()));
if (Objects.nonNull(e.getFallbackInfo())) {
result.setRetCode(e.getFallbackInfo().getCode());
}
consumerAPI.updateServiceCallResult(result);
}
catch (Throwable ex) {
LOG.error("[CircuitBreaker] report circuitbreaker call result fail ", ex);
}
}
}

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

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

@ -0,0 +1,72 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.utils;
import java.util.HashMap;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.mockito.ArgumentMatchers.anyString;
public class PolarisCircuitBreakerUtilsTests {
private static MockedStatic<ApplicationContextAwareUtils> 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<>(), "")));
}
}

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

@ -23,6 +23,7 @@ import java.time.Duration;
import java.util.Objects;
import java.util.Set;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
@ -30,6 +31,7 @@ import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentReactiv
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback;
import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils;
import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.Argument;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
@ -115,6 +117,10 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
dataBuffer = response.bufferFactory().allocateBuffer()
.write(rejectTips.getBytes(StandardCharsets.UTF_8));
}
response.getHeaders().add(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc());
if (Objects.nonNull(quotaResponse.getActiveRule())) {
response.getHeaders().add(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, quotaResponse.getActiveRule().getName().getValue());
}
return response.writeWith(Mono.just(dataBuffer));
}
// Unirate

@ -22,6 +22,7 @@ import java.io.IOException;
import java.util.Objects;
import java.util.Set;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
@ -29,6 +30,7 @@ import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServlet
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback;
import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils;
import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.Argument;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
@ -109,6 +111,10 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(rejectTips);
}
response.addHeader(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc());
if (Objects.nonNull(quotaResponse.getActiveRule())) {
response.addHeader(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, quotaResponse.getActiveRule().getName().getValue());
}
return;
}
// Unirate

@ -27,7 +27,9 @@ import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.StringValue;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
@ -94,7 +96,9 @@ public class QuotaCheckReactiveFilterTest {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
QuotaResponse response = new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
response.setActiveRule(RateLimitProto.Rule.newBuilder().setName(StringValue.newBuilder().setValue("MOCK_RULE").build()).build());
return response;
}
else {
return new QuotaResponse(new QuotaResult(null, 0, null));
@ -181,6 +185,7 @@ public class QuotaCheckReactiveFilterTest {
ServerHttpResponse response = testApp3Exchange.getResponse();
assertThat(response.getRawStatusCode()).isEqualTo(419);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE);
assertThat(response.getHeaders().get(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME)).isEqualTo(Collections.singletonList("MOCK_RULE"));
// Exception
MetadataContext.LOCAL_SERVICE = "TestApp4";

@ -27,7 +27,9 @@ import java.util.Collections;
import java.util.stream.Collectors;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.StringValue;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
@ -93,7 +95,9 @@ public class QuotaCheckServletFilterTest {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
QuotaResponse response = new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
response.setActiveRule(RateLimitProto.Rule.newBuilder().setName(StringValue.newBuilder().setValue("MOCK_RULE").build()).build());
return response;
}
else {
return new QuotaResponse(new QuotaResult(null, 0, null));
@ -179,6 +183,7 @@ public class QuotaCheckServletFilterTest {
quotaCheckServletFilter.doFilterInternal(request, testApp3Response, filterChain);
assertThat(testApp3Response.getStatus()).isEqualTo(419);
assertThat(testApp3Response.getContentAsString()).isEqualTo("RejectRequestTips提示消息");
assertThat(testApp3Response.getHeader(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME)).isEqualTo("MOCK_RULE");
MockHttpServletResponse testApp3Response2 = new MockHttpServletResponse();
quotaCheckWithHtmlRejectTipsServletFilter.doFilterInternal(request, testApp3Response2, filterChain);

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

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

@ -1,2 +0,0 @@
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.plugin.pushgateway.PolarisStatPushGatewayBootstrapConfiguration

@ -50,6 +50,18 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-test-common</artifactId>

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

@ -30,7 +30,11 @@ import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.ExceptionPolarisR
import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.SuccessPolarisReporter;
import com.tencent.cloud.rpc.enhancement.resttemplate.BlockingLoadBalancerClientAspect;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter;
import com.tencent.cloud.rpc.enhancement.scg.EnhancedPolarisHttpClientCustomizer;
import com.tencent.cloud.rpc.enhancement.scg.EnhancedPolarisHttpHeadersFilter;
import com.tencent.cloud.rpc.enhancement.webclient.EnhancedWebClientReporter;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.client.api.SDKContext;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
@ -42,11 +46,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.gateway.config.HttpClientCustomizer;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Role;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
/**
* Auto Configuration for Polaris {@link feign.Feign} OR {@link RestTemplate} which can automatically bring in the call
@ -89,14 +96,16 @@ public class RpcEnhancementAutoConfiguration {
@Bean
public SuccessPolarisReporter successPolarisReporter(RpcEnhancementReporterProperties properties,
@Autowired(required = false) ConsumerAPI consumerAPI) {
return new SuccessPolarisReporter(properties, consumerAPI);
@Autowired(required = false) SDKContext context,
@Autowired(required = false) ConsumerAPI consumerAPI) {
return new SuccessPolarisReporter(properties, context, consumerAPI);
}
@Bean
public ExceptionPolarisReporter exceptionPolarisReporter(RpcEnhancementReporterProperties properties,
@Autowired(required = false) ConsumerAPI consumerAPI) {
return new ExceptionPolarisReporter(properties, consumerAPI);
@Autowired(required = false) SDKContext context,
@Autowired(required = false) ConsumerAPI consumerAPI) {
return new ExceptionPolarisReporter(properties, context, consumerAPI);
}
}
}
@ -117,8 +126,8 @@ public class RpcEnhancementAutoConfiguration {
@Bean
public EnhancedRestTemplateReporter enhancedRestTemplateReporter(
RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) {
return new EnhancedRestTemplateReporter(properties, consumerAPI);
RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
return new EnhancedRestTemplateReporter(properties, context, consumerAPI);
}
@Bean
@ -137,4 +146,44 @@ public class RpcEnhancementAutoConfiguration {
return new BlockingLoadBalancerClientAspect();
}
}
/**
* Configuration for Polaris {@link org.springframework.web.reactive.function.client.WebClient} which can automatically bring in the call
* results for reporting.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.web.reactive.function.client.WebClient")
protected static class PolarisWebClientAutoConfiguration {
@Bean
public ExchangeFilterFunction exchangeFilterFunction(
RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
return new EnhancedWebClientReporter(properties, context, consumerAPI);
}
}
/**
* Configuration for Polaris {@link org.springframework.web.reactive.function.client.WebClient} which can automatically bring in the call
* results for reporting.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.cloud.gateway.config.GatewayAutoConfiguration")
@Role(RootBeanDefinition.ROLE_INFRASTRUCTURE)
protected static class PolarisGatewayAutoConfiguration {
@Bean
@ConditionalOnClass(name = {"org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter"})
public HttpHeadersFilter enhancedPolarisHttpHeadersFilter() {
return new EnhancedPolarisHttpHeadersFilter();
}
@Bean
@ConditionalOnClass(name = {"org.springframework.cloud.gateway.config.HttpClientCustomizer"})
public HttpClientCustomizer httpClientCustomizer(
RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
return new EnhancedPolarisHttpClientCustomizer(properties, context, consumerAPI);
}
}
}

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

@ -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<ServiceCallResult> consumer) {
ServiceCallResult resultRequest = new ServiceCallResult();
resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE);
@ -65,7 +70,7 @@ public final class ReporterUtils {
catch (UnsupportedEncodingException e) {
LOGGER.error("unsupported charset exception " + UTF_8, e);
}
resultRequest.setLabels(convertLabel(label));
resultRequest.setLabels(RequestLabelUtils.convertLabel(label));
}
URI uri = URI.create(request.url());
resultRequest.setMethod(uri.getPath());
@ -82,16 +87,14 @@ public final class ReporterUtils {
if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) {
resultRequest.setCallerService(new ServiceKey(sourceNamespace, sourceService));
}
if (Objects.nonNull(context)) {
resultRequest.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP());
}
resultRequest.setHost(uri.getHost());
// -1 means access directly by url, and use http default port number 80
resultRequest.setPort(uri.getPort() == -1 ? 80 : uri.getPort());
consumer.accept(resultRequest);
return resultRequest;
}
private static String convertLabel(String label) {
label = label.replaceAll("\"|\\{|\\}", "")
.replaceAll(",", "|");
return label;
}
}

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

@ -23,10 +23,12 @@ import java.net.URI;
import java.net.URLDecoder;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.tencent.cloud.common.constant.RouterConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.RequestLabelUtils;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
@ -34,6 +36,7 @@ import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.client.api.SDKContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -68,10 +71,12 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter
public static final String HEADER_HAS_ERROR = "X-SCT-Has-Error";
private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedRestTemplateReporter.class);
private final ConsumerAPI consumerAPI;
private final SDKContext context;
private ResponseErrorHandler delegateHandler;
public EnhancedRestTemplateReporter(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) {
public EnhancedRestTemplateReporter(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) {
super(properties);
this.context = context;
this.consumerAPI = consumerAPI;
}
@ -153,7 +158,11 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter
if (apply(httpStatus)) {
resultRequest.setRetStatus(RetStatus.RetFail);
}
resultRequest.setRetStatus(getRetStatusFromRequest(response.getHeaders(), resultRequest.getRetStatus()));
resultRequest.setRuleName(getActiveRuleNameFromRequest(response.getHeaders()));
if (Objects.nonNull(context)) {
resultRequest.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP());
}
List<String> labels = response.getHeaders().get(RouterConstant.ROUTER_LABEL_HEADER);
if (CollectionUtils.isNotEmpty(labels)) {
String label = labels.get(0);
@ -163,7 +172,7 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter
catch (UnsupportedEncodingException e) {
LOGGER.error("unsupported charset exception " + UTF_8, e);
}
resultRequest.setLabels(convertLabel(label));
resultRequest.setLabels(RequestLabelUtils.convertLabel(label));
}
// processing report with consumerAPI .
@ -176,12 +185,6 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter
}
}
private String convertLabel(String label) {
label = label.replaceAll("\"|\\{|\\}", "")
.replaceAll(",", "|");
return label;
}
private void invokeDelegateHandler(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
if (realHasError(response)) {
delegateHandler.handleError(url, method, response);

@ -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<? super HttpClientResponse, ? super Throwable> handler = new BiConsumer<HttpClientResponse, Throwable>() {
@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<String, String> 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);
}
}
}

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

@ -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<ServiceInstance> 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<String> values = headers.get(name);
String delimitedValue = StringUtils.collectionToCommaDelimitedString(values);
headers.set(name, delimitedValue);
}
else {
headers.set(name, value);
}
}
}

@ -0,0 +1,145 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.webclient;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.Objects;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.common.constant.RouterConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.RequestLabelUtils;
import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.client.api.SDKContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import reactor.util.context.ContextView;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
public class EnhancedWebClientReporter extends AbstractPolarisReporterAdapter implements ExchangeFilterFunction {
protected static final String METRICS_WEBCLIENT_START_TIME = EnhancedWebClientReporter.class.getName()
+ ".START_TIME";
private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedWebClientReporter.class);
private final ConsumerAPI consumerAPI;
private final SDKContext context;
public EnhancedWebClientReporter(RpcEnhancementReporterProperties reportProperties, SDKContext context, ConsumerAPI consumerAPI) {
super(reportProperties);
this.context = context;
this.consumerAPI = consumerAPI;
}
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
return next.exchange(request).as((responseMono) -> instrumentResponse(request, responseMono))
.contextWrite(this::putStartTime);
}
Mono<ClientResponse> instrumentResponse(ClientRequest request, Mono<ClientResponse> 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<String> labels = request.headers().get(RouterConstant.ROUTER_LABEL_HEADER);
if (CollectionUtils.isNotEmpty(labels) && labels.iterator().hasNext()) {
String label = labels.iterator().next();
try {
label = URLDecoder.decode(label, UTF_8);
}
catch (UnsupportedEncodingException e) {
LOGGER.error("unsupported charset exception " + UTF_8, e);
}
callResult.setLabels(RequestLabelUtils.convertLabel(label));
}
URI uri = request.url();
callResult.setMethod(uri.getPath());
callResult.setHost(uri.getHost());
// -1 means access directly by url, and use http default port number 80
callResult.setPort(uri.getPort() == -1 ? 80 : uri.getPort());
if (Objects.nonNull(context)) {
callResult.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP());
}
RetStatus retStatus = RetStatus.RetSuccess;
ClientResponse response = signal.get();
if (Objects.nonNull(response)) {
HttpHeaders headers = response.headers().asHttpHeaders();
callResult.setRuleName(getActiveRuleNameFromRequest(headers));
if (apply(HttpStatus.valueOf(response.statusCode().value()))) {
retStatus = RetStatus.RetFail;
}
retStatus = getRetStatusFromRequest(headers, retStatus);
}
if (signal.isOnError()) {
Throwable throwable = signal.getThrowable();
if (throwable instanceof SocketTimeoutException) {
retStatus = RetStatus.RetTimeout;
}
}
callResult.setRetStatus(retStatus);
consumerAPI.updateServiceCallResult(callResult);
}));
}
private Long getStartTime(ContextView context) {
return context.get(METRICS_WEBCLIENT_START_TIME);
}
private Context putStartTime(Context context) {
return context.put(METRICS_WEBCLIENT_START_TIME, System.currentTimeMillis());
}
}

@ -17,10 +17,13 @@
package com.tencent.cloud.rpc.enhancement;
import com.tencent.cloud.common.constant.HeaderConstant;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.pojo.RetStatus;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
/**
@ -104,6 +107,48 @@ public class AbstractPolarisReporterAdapterTest {
Assertions.assertThat(adapter.apply(HttpStatus.FORBIDDEN)).isEqualTo(true);
}
@Test
public void testGetRetStatusFromRequest() {
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
// Mock Condition
properties.getStatuses().clear();
properties.getSeries().clear();
properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
HttpHeaders headers = new HttpHeaders();
RetStatus ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail);
Assertions.assertThat(ret).isEqualTo(RetStatus.RetFail);
headers.set(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc());
ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail);
Assertions.assertThat(ret).isEqualTo(RetStatus.RetFlowControl);
headers.set(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetReject.getDesc());
ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail);
Assertions.assertThat(ret).isEqualTo(RetStatus.RetReject);
}
@Test
public void testGetActiveRuleNameFromRequest() {
RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties();
// Mock Condition
properties.getStatuses().clear();
properties.getSeries().clear();
properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR);
SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties);
HttpHeaders headers = new HttpHeaders();
String ruleName = adapter.getActiveRuleNameFromRequest(headers);
Assertions.assertThat(ruleName).isEqualTo("");
headers.set(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, "mock_rule");
ruleName = adapter.getActiveRuleNameFromRequest(headers);
Assertions.assertThat(ruleName).isEqualTo("mock_rule");
}
/**
* Simple Polaris CircuitBreak Adapter Implements .
*/

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

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

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

@ -18,17 +18,24 @@
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
import java.util.HashMap;
import java.util.function.Consumer;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.client.api.SDKContext;
import feign.Request;
import feign.RequestTemplate;
import feign.Response;
import feign.Target;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@ -37,9 +44,12 @@ import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@ -53,6 +63,7 @@ import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
public class ExceptionPolarisReporterTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ReporterUtils> mockedReporterUtils;
@Mock
private ConsumerAPI consumerAPI;
@ -63,16 +74,27 @@ public class ExceptionPolarisReporterTest {
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class);
mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(Request.class), any(Response.class), anyLong(), any(RetStatus.class)))
.thenReturn(mock(ServiceCallResult.class));
mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class),
any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class)))
.thenReturn(new ServiceCallResult());
}
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
mockedReporterUtils.close();
}
@BeforeEach
void setUp() {
MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST;
MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER;
}
@Test
public void testGetName() {
assertThat(exceptionPolarisReporter.getName()).isEqualTo(ExceptionPolarisReporter.class.getName());
@ -100,6 +122,32 @@ public class ExceptionPolarisReporterTest {
doReturn(true).when(reporterProperties).isEnabled();
exceptionPolarisReporter.run(context);
verify(context, times(1)).getRequest();
try {
mockedReporterUtils.close();
// mock target
Target<?> target = mock(Target.class);
doReturn(SERVICE_PROVIDER).when(target).name();
// mock RequestTemplate.class
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.feignTarget(target);
EnhancedFeignContext feignContext = new EnhancedFeignContext();
request = Request.create(Request.HttpMethod.GET, "/", new HashMap<>(), null, null, requestTemplate);
response = Response.builder()
.request(request)
.build();
feignContext.setRequest(request);
feignContext.setResponse(response);
exceptionPolarisReporter.run(feignContext);
}
finally {
mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class);
mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class),
any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class)))
.thenReturn(new ServiceCallResult());
}
}
@Test

@ -23,8 +23,12 @@ import java.net.URLEncoder;
import com.tencent.cloud.common.constant.RouterConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.polaris.api.config.Configuration;
import com.tencent.polaris.api.config.global.APIConfig;
import com.tencent.polaris.api.config.global.GlobalConfig;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.client.api.SDKContext;
import feign.Request;
import feign.RequestTemplate;
import feign.Response;
@ -99,7 +103,9 @@ public class ReporterUtilsTest {
Response response = mock(Response.class);
doReturn(502).when(response).status();
ServiceCallResult serviceCallResult = ReporterUtils.createServiceCallResult(request, response, 10L, RetStatus.RetSuccess);
ServiceCallResult serviceCallResult = ReporterUtils.createServiceCallResult(mockSDKContext(), request, response, 10L, RetStatus.RetSuccess, result -> {
});
assertThat(serviceCallResult.getNamespace()).isEqualTo(NAMESPACE_TEST);
assertThat(serviceCallResult.getService()).isEqualTo(SERVICE_PROVIDER);
assertThat(serviceCallResult.getHost()).isEqualTo("1.1.1.1");
@ -112,4 +118,18 @@ public class ReporterUtilsTest {
assertThat(serviceCallResult.getRetCode()).isEqualTo(502);
assertThat(serviceCallResult.getDelay()).isEqualTo(10L);
}
public static SDKContext mockSDKContext() {
APIConfig apiConfig = mock(APIConfig.class);
doReturn("127.0.0.1").when(apiConfig).getBindIP();
GlobalConfig globalConfig = mock(GlobalConfig.class);
doReturn(apiConfig).when(globalConfig).getAPI();
Configuration configuration = mock(Configuration.class);
doReturn(globalConfig).when(configuration).getGlobal();
SDKContext context = mock(SDKContext.class);
doReturn(configuration).when(context).getConfig();
return context;
}
}

@ -18,17 +18,24 @@
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter;
import java.util.HashMap;
import java.util.function.Consumer;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import com.tencent.polaris.client.api.SDKContext;
import feign.Request;
import feign.RequestTemplate;
import feign.Response;
import feign.Target;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@ -37,9 +44,12 @@ import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@ -53,6 +63,7 @@ import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
public class SuccessPolarisReporterTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ReporterUtils> mockedReporterUtils;
@Mock
private ConsumerAPI consumerAPI;
@ -63,16 +74,27 @@ public class SuccessPolarisReporterTest {
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class);
mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(Request.class), any(Response.class), anyLong(), any(RetStatus.class)))
.thenReturn(mock(ServiceCallResult.class));
mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class),
any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class)))
.thenReturn(new ServiceCallResult());
}
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
mockedReporterUtils.close();
}
@BeforeEach
void setUp() {
MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST;
MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER;
}
@Test
public void testGetName() {
assertThat(successPolarisReporter.getName()).isEqualTo(SuccessPolarisReporter.class.getName());
@ -101,6 +123,32 @@ public class SuccessPolarisReporterTest {
doReturn(true).when(reporterProperties).isEnabled();
successPolarisReporter.run(context);
verify(context, times(1)).getRequest();
try {
mockedReporterUtils.close();
// mock target
Target<?> target = mock(Target.class);
doReturn(SERVICE_PROVIDER).when(target).name();
// mock RequestTemplate.class
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.feignTarget(target);
EnhancedFeignContext feignContext = new EnhancedFeignContext();
request = Request.create(Request.HttpMethod.GET, "/", new HashMap<>(), null, null, requestTemplate);
response = Response.builder()
.request(request)
.build();
feignContext.setRequest(request);
feignContext.setResponse(response);
successPolarisReporter.run(feignContext);
}
finally {
mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class);
mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(SDKContext.class), any(Request.class),
any(Response.class), anyLong(), any(RetStatus.class), any(Consumer.class)))
.thenReturn(new ServiceCallResult());
}
}
@Test

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

@ -0,0 +1,57 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.rpc.enhancement.scg;
import java.util.Collections;
import com.tencent.cloud.common.constant.HeaderConstant;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR;
public class EnhancedPolarisHttpHeadersFilterTest {
@Test
public void testFilter() {
EnhancedPolarisHttpHeadersFilter filter = new EnhancedPolarisHttpHeadersFilter();
ServiceInstance instance = Mockito.mock(ServiceInstance.class);
Mockito.doReturn("mock_service").when(instance).getServiceId();
Mockito.doReturn("127.0.0.1").when(instance).getHost();
Mockito.doReturn(8080).when(instance).getPort();
DefaultResponse response = new DefaultResponse(instance);
ServerWebExchange exchange = Mockito.mock(ServerWebExchange.class);
Mockito.doReturn(response).when(exchange).getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR);
HttpHeaders input = new HttpHeaders();
HttpHeaders headers = filter.filter(input, exchange);
Assertions.assertEquals(Collections.singletonList("mock_service"), headers.get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID));
Assertions.assertEquals(Collections.singletonList("127.0.0.1"), headers.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST));
Assertions.assertEquals(Collections.singletonList("8080"), headers.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT));
}
}

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

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

@ -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<ApplicationContextAwareUtils> 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<ClientResponse> 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();
}
}
Loading…
Cancel
Save