diff --git a/CHANGELOG.md b/CHANGELOG.md index 06c1f8175..85aeb11af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ # Change Log --- +- [feature:add PolarisRateLimiterLimitedFallback spi.](https://github.com/Tencent/spring-cloud-tencent/commit/52689515f5dd7f1ddf22dbe9b62fcd9d19fc8dbe) +- [fix:fix the error capture of rate limit exception.](https://github.com/Tencent/spring-cloud-tencent/commit/edeeaded922ee3837eb5a58582a62f1f4c77608e) +- [feat:enable stat reporting as default.](https://github.com/Tencent/spring-cloud-tencent/commit/f97a1b2450bfba047d22e96a85e2ab39e849f4a0) +- [refactor:update to junit 5.](https://github.com/Tencent/spring-cloud-tencent/commit/21953e17b6549e0ce52fc4df0a94f96a10dbe0c5) +- [docs:support auto snapshot release in GitHub Action.](https://github.com/Tencent/spring-cloud-tencent/commit/da2422ae71f83d229e8c322ce2ccd5850ffa8653) +- [feature:add User-Agent:polaris for healthyCheck api.](https://github.com/Tencent/spring-cloud-tencent/commit/b2cecba273bfd0eaf5160791c1efe3acb01d0bfe) +- [optimize ServiceRuleManager](https://github.com/Tencent/spring-cloud-tencent/commit/07f04174ca487cf1db1d9c19bbe44853e2bcf117) +- [refactor:refactor stat module.](https://github.com/Tencent/spring-cloud-tencent/commit/9408bfbb948446738c050b3b18d8ed3124da36c1) +- [fix:fix NPE.](https://github.com/Tencent/spring-cloud-tencent/commit/17a9a8d75aef6ed39a0a1c7aa4b7d547f5d3d40e) +- [refactor:optimize sct-all.](https://github.com/Tencent/spring-cloud-tencent/commit/3859cd7eefcd93752d05113597656e584d97c38d) +- [feature:add polaris circuit breaker support]() \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9ee70db2c..0117853f3 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ <properties> <!-- Project revision --> - <revision>1.11.1-Hoxton.SR12-SNAPSHOT</revision> + <revision>1.11.2-Hoxton.SR12-SNAPSHOT</revision> <!-- Spring Framework --> <spring.framework.version>5.2.22.RELEASE</spring.framework.version> diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java index a8966025d..d3bfe31e4 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java @@ -29,6 +29,7 @@ import com.tencent.cloud.metadata.core.DecodeTransferMetadataServletFilter; import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor; import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor; import com.tencent.cloud.metadata.core.EncodeTransferMedataScgFilter; +import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientFilter; import com.tencent.cloud.metadata.core.EncodeTransferMetadataZuulFilter; import org.springframework.beans.factory.SmartInitializingSingleton; @@ -41,6 +42,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; import static javax.servlet.DispatcherType.ASYNC; import static javax.servlet.DispatcherType.ERROR; @@ -66,10 +68,12 @@ public class MetadataTransferAutoConfiguration { @Bean public FilterRegistrationBean<DecodeTransferMetadataServletFilter> metadataServletFilterRegistrationBean( DecodeTransferMetadataServletFilter decodeTransferMetadataServletFilter) { - FilterRegistrationBean<DecodeTransferMetadataServletFilter> filterRegistrationBean = - new FilterRegistrationBean<>(decodeTransferMetadataServletFilter); - filterRegistrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, REQUEST); - filterRegistrationBean.setOrder(MetadataConstant.OrderConstant.WEB_FILTER_ORDER); + FilterRegistrationBean<DecodeTransferMetadataServletFilter> filterRegistrationBean = new FilterRegistrationBean<>( + decodeTransferMetadataServletFilter); + filterRegistrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, + REQUEST); + filterRegistrationBean + .setOrder(MetadataConstant.OrderConstant.WEB_FILTER_ORDER); return filterRegistrationBean; } @@ -77,7 +81,6 @@ public class MetadataTransferAutoConfiguration { public DecodeTransferMetadataServletFilter metadataServletFilter() { return new DecodeTransferMetadataServletFilter(); } - } /** @@ -133,7 +136,6 @@ public class MetadataTransferAutoConfiguration { public EncodeTransferMedataFeignInterceptor encodeTransferMedataFeignInterceptor() { return new EncodeTransferMedataFeignInterceptor(); } - } /** @@ -160,4 +162,26 @@ public class MetadataTransferAutoConfiguration { }); } } + + /** + * Create when WebClient.Builder exists. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "org.springframework.web.reactive.function.client.WebClient") + protected static class MetadataTransferWebClientConfig { + @Autowired(required = false) + private List<WebClient.Builder> webClientBuilder = Collections.emptyList(); + + @Bean + public EncodeTransferMedataWebClientFilter encodeTransferMedataWebClientFilter() { + return new EncodeTransferMedataWebClientFilter(); + } + + @Bean + public SmartInitializingSingleton addEncodeTransferMetadataFilterForWebClient(EncodeTransferMedataWebClientFilter filter) { + return () -> webClientBuilder.forEach(webClient -> { + webClient.filter(filter); + }); + } + } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilter.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilter.java new file mode 100644 index 000000000..3547add0e --- /dev/null +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilter.java @@ -0,0 +1,90 @@ +/* + * 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.metadata.core; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Map; + +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.util.JacksonUtils; +import reactor.core.publisher.Mono; + +import org.springframework.util.CollectionUtils; +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; +import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA; +import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA; + +/** + * web client filter used for writing metadata in HTTP request header. + * + * @author sean yu + */ +public class EncodeTransferMedataWebClientFilter implements ExchangeFilterFunction { + + @Override + public Mono<ClientResponse> filter(ClientRequest clientRequest, ExchangeFunction next) { + MetadataContext metadataContext = MetadataContextHolder.get(); + Map<String, String> customMetadata = metadataContext.getCustomMetadata(); + Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata(); + Map<String, String> transHeaders = metadataContext.getTransHeadersKV(); + + ClientRequest.Builder requestBuilder = ClientRequest.from(clientRequest); + + this.buildMetadataHeader(requestBuilder, customMetadata, CUSTOM_METADATA); + this.buildMetadataHeader(requestBuilder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA); + this.buildTransmittedHeader(requestBuilder, transHeaders); + + ClientRequest request = requestBuilder.build(); + + return next.exchange(request); + } + + private void buildTransmittedHeader(ClientRequest.Builder requestBuilder, Map<String, String> transHeaders) { + if (!CollectionUtils.isEmpty(transHeaders)) { + transHeaders.forEach(requestBuilder::header); + } + } + + + /** + * Set metadata into the request header for {@link ClientRequest} . + * @param requestBuilder instance of {@link ClientRequest.Builder} + * @param metadata metadata map . + * @param headerName target metadata http header name . + */ + private void buildMetadataHeader(ClientRequest.Builder requestBuilder, Map<String, String> metadata, String headerName) { + if (!CollectionUtils.isEmpty(metadata)) { + String encodedMetadata = JacksonUtils.serialize2Json(metadata); + try { + requestBuilder.header(headerName, URLEncoder.encode(encodedMetadata, UTF_8)); + } + catch (UnsupportedEncodingException e) { + requestBuilder.header(headerName, encodedMetadata); + } + } + } + +} diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java index 7f3164e3e..cd23e7615 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java @@ -26,10 +26,12 @@ import java.util.stream.Collectors; import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor; import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor; +import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientFilter; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; @@ -47,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class MetadataTransferAutoConfigurationTest { private final ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner(); + private final ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = new ReactiveWebApplicationContextRunner(); /** * No any web application. @@ -88,6 +91,23 @@ public class MetadataTransferAutoConfigurationTest { }); } + /** + * Reactive web application. + */ + @Test + public void test3() { + this.reactiveWebApplicationContextRunner.withConfiguration(AutoConfigurations.of(MetadataTransferAutoConfiguration.class)) + .run(context -> { + assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferFeignInterceptorConfig.class); + assertThat(context).hasSingleBean(EncodeTransferMedataFeignInterceptor.class); + assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferRestTemplateConfig.class); + assertThat(context).hasSingleBean(EncodeTransferMedataRestTemplateInterceptor.class); + assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferScgFilterConfig.class); + assertThat(context).hasSingleBean(GlobalFilter.class); + assertThat(context).hasSingleBean(EncodeTransferMedataWebClientFilter.class); + }); + } + @Configuration static class RestTemplateConfiguration { diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilterTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilterTest.java new file mode 100644 index 000000000..85c8b2d35 --- /dev/null +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilterTest.java @@ -0,0 +1,83 @@ +/* + * 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.metadata.core; + +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Test for {@link EncodeTransferMedataWebClientFilter}. + * + * @author sean yu + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, + classes = EncodeTransferMedataWebClientFilterTest.TestApplication.class, + properties = {"spring.config.location = classpath:application-test.yml", + "spring.main.web-application-type = reactive"}) +public class EncodeTransferMedataWebClientFilterTest { + + @Autowired + private WebClient.Builder webClientBuilder; + @LocalServerPort + private int localServerPort; + + @Test + public void testTransitiveMetadataFromApplicationConfig() { + MetadataContext metadataContext = MetadataContextHolder.get(); + metadataContext.setTransHeadersKV("xxx", "xxx"); + String metadata = webClientBuilder.baseUrl("http://localhost:" + localServerPort).build() + .get() + .uri("/test") + .retrieve() + .bodyToMono(String.class) + .block(); + assertThat(metadata).isEqualTo("2"); + } + + @SpringBootApplication + @RestController + protected static class TestApplication { + + @Bean + public WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } + + @RequestMapping("/test") + public String test() { + return MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "b"); + } + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml b/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml index 20bf4bdf2..5cc0cff59 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml @@ -15,8 +15,34 @@ <dependencies> <!-- Spring Cloud Tencent dependencies start --> <dependency> - <groupId>com.tencent.cloud</groupId> - <artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-webflux</artifactId> + <optional>true</optional> + </dependency> + + + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-openfeign</artifactId> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-gateway</artifactId> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-netflix-zuul</artifactId> + <optional>true</optional> </dependency> <dependency> @@ -60,6 +86,21 @@ </exclusion> </exclusions> </dependency> + + <dependency> + <groupId>com.tencent.polaris</groupId> + <artifactId>healthchecker-http</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.polaris</groupId> + <artifactId>healthchecker-udp</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.polaris</groupId> + <artifactId>healthchecker-tcp</artifactId> + </dependency> <!-- Polaris dependencies end --> <dependency> @@ -67,5 +108,35 @@ <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> + + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-contract-stub-runner</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>com.tencent.polaris</groupId> + <artifactId>polaris-test-common</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>com.tencent.polaris</groupId> + <artifactId>polaris-test-mock-discovery</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-inline</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>io.projectreactor</groupId> + <artifactId>reactor-test</artifactId> + <scope>test</scope> + </dependency> </dependencies> </project> 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 new file mode 100644 index 000000000..3f247093d --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreaker.java @@ -0,0 +1,102 @@ +/* + * 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; + +import java.util.function.Function; +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; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; + +/** + * PolarisCircuitBreaker. + * + * @author seanyu 2023-02-27 + */ +public class PolarisCircuitBreaker implements CircuitBreaker, InvokeHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(PolarisCircuitBreaker.class); + private final PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf; + private final ConsumerAPI consumerAPI; + private final FunctionalDecorator decorator; + private final InvokeHandler invokeHandler; + + 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); + this.invokeHandler = circuitBreakAPI.makeInvokeHandler(makeDecoratorRequest); + } + + @Override + public <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) { + Supplier<T> toRunDecorator = decorator.decorateSupplier(toRun); + try { + return toRunDecorator.get(); + } + catch (CallAbortedException e) { + LOGGER.debug("PolarisCircuitBreaker CallAbortedException: {}", e.getMessage()); + PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, e); + return fallback.apply(e); + } + catch (Exception e) { + return fallback.apply(e); + } + } + + @Override + public void acquirePermission() { + invokeHandler.acquirePermission(); + } + + @Override + public void onSuccess(InvokeContext.ResponseContext responseContext) { + invokeHandler.onSuccess(responseContext); + } + + @Override + public void onError(InvokeContext.ResponseContext responseContext) { + invokeHandler.onError(responseContext); + } + + public PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration getConf() { + return conf; + } + + public ConsumerAPI getConsumerAPI() { + return consumerAPI; + } +} 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 new file mode 100644 index 000000000..1c789befe --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerFactory.java @@ -0,0 +1,76 @@ +/* + * 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; + +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; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; + +/** + * PolarisCircuitBreakerFactory. + * + * @author seanyu 2023-02-27 + */ +public class PolarisCircuitBreakerFactory + extends CircuitBreakerFactory<PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration, PolarisCircuitBreakerConfigBuilder> { + + private Function<String, PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration> defaultConfiguration = + id -> { + String[] metadata = PolarisCircuitBreakerUtils.resolveCircuitBreakerId(id); + return new PolarisCircuitBreakerConfigBuilder() + .namespace(metadata[0]) + .service(metadata[1]) + .method(metadata[2]) + .build(); + }; + + + private final 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, consumerAPI, circuitBreakAPI); + } + + @Override + protected PolarisCircuitBreakerConfigBuilder configBuilder(String id) { + String[] metadata = PolarisCircuitBreakerUtils.resolveCircuitBreakerId(id); + return new PolarisCircuitBreakerConfigBuilder(metadata[0], metadata[1], metadata[2]); + } + + @Override + public void configureDefault(Function<String, PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration> defaultConfiguration) { + this.defaultConfiguration = defaultConfiguration; + } + +} 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 new file mode 100644 index 000000000..56b633ef4 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreaker.java @@ -0,0 +1,91 @@ +/* + * 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; + +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; + +import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker; + +/** + * ReactivePolarisCircuitBreaker. + * + * @author seanyu 2023-02-27 + */ +public class ReactivePolarisCircuitBreaker implements ReactiveCircuitBreaker { + + private final InvokeHandler invokeHandler; + + 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); + } + + + @Override + 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(throwable -> { + if (throwable instanceof CallAbortedException) { + PolarisCircuitBreakerUtils.reportStatus(consumerAPI, conf, (CallAbortedException) throwable); + } + return fallback.apply(throwable); + }); + } + return toReturn; + } + + @Override + 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(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 new file mode 100644 index 000000000..0a7bbb01f --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreakerFactory.java @@ -0,0 +1,76 @@ +/* + * 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; + +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; +import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; + +/** + * ReactivePolarisCircuitBreakerFactory. + * + * @author seanyu 2023-02-27 + */ +public class ReactivePolarisCircuitBreakerFactory extends + ReactiveCircuitBreakerFactory<PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration, PolarisCircuitBreakerConfigBuilder> { + + private Function<String, PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration> defaultConfiguration = + id -> { + String[] metadata = PolarisCircuitBreakerUtils.resolveCircuitBreakerId(id); + return new PolarisCircuitBreakerConfigBuilder() + .namespace(metadata[0]) + .service(metadata[1]) + .method(metadata[2]) + .build(); + }; + + private final 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, consumerAPI, circuitBreakAPI); + } + + @Override + protected PolarisCircuitBreakerConfigBuilder configBuilder(String id) { + String[] metadata = PolarisCircuitBreakerUtils.resolveCircuitBreakerId(id); + return new PolarisCircuitBreakerConfigBuilder(metadata[0], metadata[1], metadata[2]); + } + + @Override + public void configureDefault( + Function<String, PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration> defaultConfiguration) { + this.defaultConfiguration = defaultConfiguration; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/common/CircuitBreakerConfigModifier.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/common/CircuitBreakerConfigModifier.java new file mode 100644 index 000000000..220d98490 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/common/CircuitBreakerConfigModifier.java @@ -0,0 +1,62 @@ +/* + * 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.common; + +import com.tencent.cloud.common.constant.ContextConstant; +import com.tencent.cloud.polaris.context.PolarisConfigModifier; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.config.consumer.ServiceRouterConfig; +import com.tencent.polaris.factory.config.ConfigurationImpl; +import com.tencent.polaris.plugins.router.healthy.RecoverRouterConfig; + +/** + * CircuitBreakerConfigModifier. + * + * @author seanyu 2023-02-27 + */ +public class CircuitBreakerConfigModifier implements PolarisConfigModifier { + + private final RpcEnhancementReporterProperties properties; + + public CircuitBreakerConfigModifier(RpcEnhancementReporterProperties properties) { + this.properties = properties; + } + + @Override + public void modify(ConfigurationImpl configuration) { + properties.setEnabled(true); + + // Turn on circuitbreaker configuration + configuration.getConsumer().getCircuitBreaker().setEnable(true); + + // Set excludeCircuitBreakInstances to true + RecoverRouterConfig recoverRouterConfig = configuration.getConsumer().getServiceRouter() + .getPluginConfig(ServiceRouterConfig.DEFAULT_ROUTER_RECOVER, RecoverRouterConfig.class); + + recoverRouterConfig.setExcludeCircuitBreakInstances(true); + + // Update modified config to source properties + configuration.getConsumer().getServiceRouter() + .setPluginConfig(ServiceRouterConfig.DEFAULT_ROUTER_RECOVER, recoverRouterConfig); + } + + @Override + public int getOrder() { + return ContextConstant.ModifierOrder.CIRCUIT_BREAKER_ORDER; + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisCircuitBreakerConfigBuilder.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisCircuitBreakerConfigBuilder.java new file mode 100644 index 000000000..733180491 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisCircuitBreakerConfigBuilder.java @@ -0,0 +1,116 @@ +/* + * 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.common; + +import com.tencent.cloud.common.metadata.MetadataContext; + +import org.springframework.cloud.client.circuitbreaker.ConfigBuilder; + +/** + * PolarisCircuitBreakerConfigBuilder. + * + * @author seanyu 2023-02-27 + */ +public class PolarisCircuitBreakerConfigBuilder implements ConfigBuilder<PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration> { + + private String namespace = MetadataContext.LOCAL_NAMESPACE; + + private String service; + + private String method; + + public PolarisCircuitBreakerConfigBuilder() { + + } + + public PolarisCircuitBreakerConfigBuilder(String namespace, String service, String method) { + this.namespace = namespace; + this.service = service; + this.method = method; + } + + public PolarisCircuitBreakerConfigBuilder namespace(String namespace) { + this.namespace = namespace; + return this; + } + + public PolarisCircuitBreakerConfigBuilder service(String service) { + this.service = service; + return this; + } + + public PolarisCircuitBreakerConfigBuilder method(String method) { + this.method = method; + return this; + } + + @Override + public PolarisCircuitBreakerConfiguration build() { + PolarisCircuitBreakerConfiguration conf = new PolarisCircuitBreakerConfiguration(); + conf.setNamespace(namespace); + conf.setService(service); + conf.setMethod(method); + return conf; + } + + public static class PolarisCircuitBreakerConfiguration { + + private final String sourceNamespace = MetadataContext.LOCAL_NAMESPACE; + + private final String sourceService = MetadataContext.LOCAL_SERVICE; + + private String namespace; + + private String service; + + private String method; + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getService() { + return service; + } + + public void setService(String service) { + this.service = service; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getSourceNamespace() { + return sourceNamespace; + } + + public String getSourceService() { + return sourceService; + } + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisResultToErrorCode.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisResultToErrorCode.java new file mode 100644 index 000000000..ec47e5f38 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisResultToErrorCode.java @@ -0,0 +1,69 @@ +/* + * 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.common; + +import com.tencent.cloud.polaris.circuitbreaker.zuul.PolarisCircuitBreakerPostZuulFilter; +import com.tencent.polaris.circuitbreak.api.pojo.ResultToErrorCode; +import feign.FeignException; + +import org.springframework.web.client.RestClientResponseException; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +/** + * PolarisResultToErrorCode. + * + * @author seanyu 2023-02-27 + */ +public class PolarisResultToErrorCode implements ResultToErrorCode { + + @Override + public int onSuccess(Object value) { + return 200; + } + + @Override + public int onError(Throwable e) { + if (checkClassExist("org.springframework.web.client.RestClientResponseException") + && e instanceof RestClientResponseException) { + return ((RestClientResponseException) e).getRawStatusCode(); + } + else if (checkClassExist("feign.FeignException") + && e instanceof FeignException) { + return ((FeignException) e).status(); + } + else if (checkClassExist("org.springframework.web.reactive.function.client.WebClientResponseException") + && e instanceof WebClientResponseException) { + return ((WebClientResponseException) e).getRawStatusCode(); + } + else if (e instanceof PolarisCircuitBreakerPostZuulFilter.CircuitBreakerStatusCodeException) { + return ((PolarisCircuitBreakerPostZuulFilter.CircuitBreakerStatusCodeException) e).getRawStatusCode(); + } + return -1; + } + + private boolean checkClassExist(String clazzName) { + try { + Class.forName(clazzName, false, getClass().getClassLoader()); + } + catch (ClassNotFoundException e) { + return false; + } + return true; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/GatewayPolarisCircuitBreakerAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/GatewayPolarisCircuitBreakerAutoConfiguration.java new file mode 100644 index 000000000..e97358015 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/GatewayPolarisCircuitBreakerAutoConfiguration.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.config; + +import com.tencent.cloud.polaris.circuitbreaker.ReactivePolarisCircuitBreakerFactory; +import com.tencent.cloud.polaris.circuitbreaker.gateway.PolarisCircuitBreakerFilterFactory; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; +import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.gateway.config.GatewayAutoConfiguration; +import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledFilter; +import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties; +import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.DispatcherHandler; + +/** + * GatewayPolarisCircuitBreakerAutoConfiguration. + * + * @author seanyu 2023-02-27 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) +@AutoConfigureAfter({ReactivePolarisCircuitBreakerAutoConfiguration.class }) +@ConditionalOnClass({ DispatcherHandler.class, ReactivePolarisCircuitBreakerAutoConfiguration.class, + ReactiveCircuitBreakerFactory.class, ReactivePolarisCircuitBreakerFactory.class, GatewayAutoConfiguration.class}) +public class GatewayPolarisCircuitBreakerAutoConfiguration { + + @Bean + @ConditionalOnBean(ReactiveCircuitBreakerFactory.class) + @ConditionalOnEnabledFilter + public PolarisCircuitBreakerFilterFactory polarisCircuitBreakerFilterFactory( + ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory, + ObjectProvider<DispatcherHandler> dispatcherHandler, + @Autowired(required = false) ReactiveDiscoveryClient discoveryClient, + @Autowired(required = false) DiscoveryLocatorProperties properties + ) { + return new PolarisCircuitBreakerFilterFactory(reactiveCircuitBreakerFactory, dispatcherHandler, discoveryClient, properties); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledFilter + public FallbackHeadersGatewayFilterFactory fallbackHeadersGatewayFilterFactory() { + return new FallbackHeadersGatewayFilterFactory(); + } + +} 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 a245d695a..7dd7bf0b0 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 @@ -17,62 +17,134 @@ package com.tencent.cloud.polaris.circuitbreaker.config; -import com.tencent.cloud.common.constant.ContextConstant; -import com.tencent.cloud.polaris.context.PolarisConfigModifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerFactory; +import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier; +import com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter; +import com.tencent.cloud.polaris.circuitbreaker.reporter.SuccessCircuitBreakerReporter; +import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerRestTemplateBeanPostProcessor; +import com.tencent.cloud.polaris.circuitbreaker.zuul.PolarisCircuitBreakerPostZuulFilter; +import com.tencent.cloud.polaris.circuitbreaker.zuul.PolarisCircuitBreakerZuulFilter; +import com.tencent.cloud.polaris.circuitbreaker.zuul.PolarisZuulFallbackFactory; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; -import com.tencent.polaris.api.config.consumer.ServiceRouterConfig; -import com.tencent.polaris.factory.config.ConfigurationImpl; -import com.tencent.polaris.plugins.router.healthy.RecoverRouterConfig; +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; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.cloud.client.circuitbreaker.Customizer; +import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; +import org.springframework.cloud.openfeign.PolarisFeignCircuitBreakerTargeterAutoConfiguration; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.env.Environment; + /** - * Autoconfiguration at bootstrap phase. + * Autoconfiguration for PolarisCircuitBreaker. * * @author lepdou 2022-03-29 */ @Configuration(proxyBeanMethods = false) @ConditionalOnPolarisCircuitBreakerEnabled @AutoConfigureAfter(RpcEnhancementAutoConfiguration.class) +@Import({ + ReactivePolarisCircuitBreakerAutoConfiguration.class, + PolarisFeignCircuitBreakerTargeterAutoConfiguration.class, + PolarisCircuitBreakerFeignClientAutoConfiguration.class, + GatewayPolarisCircuitBreakerAutoConfiguration.class +}) public class PolarisCircuitBreakerAutoConfiguration { + @Autowired(required = false) + private List<Customizer<PolarisCircuitBreakerFactory>> customizers = new ArrayList<>(); + + { + // close zuul hystrix + System.setProperty("feign.hystrix.enabled", "false"); + System.setProperty("hystrix.command.default.circuitBreaker.enabled", "false"); + } + @Bean - public CircuitBreakerConfigModifier circuitBreakerConfigModifier(RpcEnhancementReporterProperties properties) { - return new CircuitBreakerConfigModifier(properties); + @ConditionalOnClass(name = "org.springframework.web.client.RestTemplate") + public static PolarisCircuitBreakerRestTemplateBeanPostProcessor polarisCircuitBreakerRestTemplateBeanPostProcessor( + ApplicationContext applicationContext) { + return new PolarisCircuitBreakerRestTemplateBeanPostProcessor(applicationContext); } - public static class CircuitBreakerConfigModifier implements PolarisConfigModifier { + @Bean + @ConditionalOnMissingBean(CircuitBreakAPI.class) + public CircuitBreakAPI circuitBreakAPI(SDKContext polarisContext) { + return CircuitBreakAPIFactory.createCircuitBreakAPIByContext(polarisContext); + } + + @Bean + @ConditionalOnMissingBean(SuccessCircuitBreakerReporter.class) + public SuccessCircuitBreakerReporter successCircuitBreakerReporter(RpcEnhancementReporterProperties properties, + SDKContext polarisContext, CircuitBreakAPI circuitBreakAPI) { + return new SuccessCircuitBreakerReporter(properties, polarisContext, circuitBreakAPI); + } - private final RpcEnhancementReporterProperties properties; + @Bean + @ConditionalOnMissingBean(ExceptionCircuitBreakerReporter.class) + public ExceptionCircuitBreakerReporter exceptionCircuitBreakerReporter(RpcEnhancementReporterProperties properties, + SDKContext polarisContext, CircuitBreakAPI circuitBreakAPI) { + return new ExceptionCircuitBreakerReporter(properties, polarisContext, circuitBreakAPI); + } - public CircuitBreakerConfigModifier(RpcEnhancementReporterProperties properties) { - this.properties = properties; - } + @Bean + @ConditionalOnMissingBean(CircuitBreakerFactory.class) + public CircuitBreakerFactory polarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) { + PolarisCircuitBreakerFactory factory = new PolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI); + customizers.forEach(customizer -> customizer.customize(factory)); + return factory; + } - @Override - public void modify(ConfigurationImpl configuration) { - properties.setEnabled(true); + @Bean + @ConditionalOnBean(RpcEnhancementReporterProperties.class) + @ConditionalOnMissingBean(CircuitBreakerConfigModifier.class) + public CircuitBreakerConfigModifier circuitBreakerConfigModifier(RpcEnhancementReporterProperties properties) { + return new CircuitBreakerConfigModifier(properties); + } - // Turn on circuitbreaker configuration - configuration.getConsumer().getCircuitBreaker().setEnable(true); + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "com.netflix.zuul.http.ZuulServlet") + protected static class PolarisCircuitBreakerZuulFilterConfig { - // Set excludeCircuitBreakInstances to true - RecoverRouterConfig recoverRouterConfig = configuration.getConsumer().getServiceRouter() - .getPluginConfig(ServiceRouterConfig.DEFAULT_ROUTER_RECOVER, RecoverRouterConfig.class); + @Autowired(required = false) + private Set<FallbackProvider> zuulFallbackProviders = Collections.emptySet(); - recoverRouterConfig.setExcludeCircuitBreakInstances(true); + @Bean + public PolarisZuulFallbackFactory polarisZuulFallbackFactory() { + return new PolarisZuulFallbackFactory(zuulFallbackProviders); + } - // Update modified config to source properties - configuration.getConsumer().getServiceRouter() - .setPluginConfig(ServiceRouterConfig.DEFAULT_ROUTER_RECOVER, recoverRouterConfig); + @Bean + public PolarisCircuitBreakerZuulFilter polarisCircuitBreakerZuulFilter( + CircuitBreakerFactory circuitBreakerFactory, + PolarisZuulFallbackFactory polarisZuulFallbackFactory, + Environment environment) { + return new PolarisCircuitBreakerZuulFilter(circuitBreakerFactory, polarisZuulFallbackFactory, environment); } - @Override - public int getOrder() { - return ContextConstant.ModifierOrder.CIRCUIT_BREAKER_ORDER; + @Bean + public PolarisCircuitBreakerPostZuulFilter polarisCircuitBreakerPostZuulFilter( + PolarisZuulFallbackFactory polarisZuulFallbackFactory, + Environment environment) { + return new PolarisCircuitBreakerPostZuulFilter(polarisZuulFallbackFactory, environment); } } } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfiguration.java index 9d21fbf0d..abf1e8e65 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfiguration.java @@ -17,7 +17,10 @@ package com.tencent.cloud.polaris.circuitbreaker.config; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; +import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; + import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -27,8 +30,8 @@ import org.springframework.context.annotation.Import; * @author lepdou 2022-03-29 */ @Configuration(proxyBeanMethods = false) -@ConditionalOnProperty("spring.cloud.polaris.enabled") -@Import(PolarisCircuitBreakerAutoConfiguration.class) +@ConditionalOnPolarisEnabled +@Import({PolarisContextAutoConfiguration.class, RpcEnhancementAutoConfiguration.class, PolarisCircuitBreakerAutoConfiguration.class}) public class PolarisCircuitBreakerBootstrapConfiguration { } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerFeignClientAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerFeignClientAutoConfiguration.java new file mode 100644 index 000000000..580ef128d --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerFeignClientAutoConfiguration.java @@ -0,0 +1,56 @@ +/* + * 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.config; + +import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisCircuitBreakerNameResolver; +import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignCircuitBreaker; +import feign.Feign; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.cloud.openfeign.FeignClientFactoryBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +/** + * PolarisCircuitBreakerFeignClientAutoConfiguration. + * + * @author seansyyu 2023-02-28 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({Feign.class, FeignClientFactoryBean.class}) +@ConditionalOnPolarisCircuitBreakerEnabled +public class PolarisCircuitBreakerFeignClientAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public PolarisCircuitBreakerNameResolver polarisCircuitBreakerNameResolver() { + return new PolarisCircuitBreakerNameResolver(); + } + + @Bean + @Scope("prototype") + @ConditionalOnBean(CircuitBreakerFactory.class) + @ConditionalOnMissingBean(Feign.Builder.class) + public Feign.Builder circuitBreakerFeignBuilder() { + return PolarisFeignCircuitBreaker.builder(); + } +} 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 new file mode 100644 index 000000000..f2f509218 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/ReactivePolarisCircuitBreakerAutoConfiguration.java @@ -0,0 +1,93 @@ +/* + * 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.config; + +import java.util.ArrayList; +import java.util.List; + +import com.tencent.cloud.polaris.circuitbreaker.ReactivePolarisCircuitBreakerFactory; +import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier; +import com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter; +import com.tencent.cloud.polaris.circuitbreaker.reporter.SuccessCircuitBreakerReporter; +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; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.client.circuitbreaker.Customizer; +import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * AutoConfiguration for ReactivePolarisCircuitBreaker. + * + * @author seanyu 2023-02-27 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(name = { "reactor.core.publisher.Mono", "reactor.core.publisher.Flux" }) +@ConditionalOnPolarisCircuitBreakerEnabled +@AutoConfigureAfter(RpcEnhancementAutoConfiguration.class) +public class ReactivePolarisCircuitBreakerAutoConfiguration { + + @Autowired(required = false) + private List<Customizer<ReactivePolarisCircuitBreakerFactory>> customizers = new ArrayList<>(); + + @Bean + @ConditionalOnMissingBean(CircuitBreakAPI.class) + public CircuitBreakAPI circuitBreakAPI(SDKContext polarisContext) { + return CircuitBreakAPIFactory.createCircuitBreakAPIByContext(polarisContext); + } + + @Bean + @ConditionalOnMissingBean(SuccessCircuitBreakerReporter.class) + public SuccessCircuitBreakerReporter successCircuitBreakerReporter(RpcEnhancementReporterProperties properties, + SDKContext polarisContext, CircuitBreakAPI circuitBreakAPI) { + return new SuccessCircuitBreakerReporter(properties, polarisContext, circuitBreakAPI); + } + + @Bean + @ConditionalOnMissingBean(ExceptionCircuitBreakerReporter.class) + public ExceptionCircuitBreakerReporter exceptionCircuitBreakerReporter(RpcEnhancementReporterProperties properties, + SDKContext polarisContext, CircuitBreakAPI circuitBreakAPI) { + return new ExceptionCircuitBreakerReporter(properties, polarisContext, circuitBreakAPI); + } + + @Bean + @ConditionalOnMissingBean(ReactiveCircuitBreakerFactory.class) + public ReactiveCircuitBreakerFactory polarisReactiveCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) { + ReactivePolarisCircuitBreakerFactory factory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI); + customizers.forEach(customizer -> customizer.customize(factory)); + return factory; + } + + @Bean + @ConditionalOnBean(RpcEnhancementReporterProperties.class) + @ConditionalOnMissingBean(CircuitBreakerConfigModifier.class) + public CircuitBreakerConfigModifier reactiveCircuitBreakerConfigModifier(RpcEnhancementReporterProperties properties) { + return new CircuitBreakerConfigModifier(properties); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisCircuitBreakerFallbackFactory.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisCircuitBreakerFallbackFactory.java new file mode 100644 index 000000000..0bb6b9644 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisCircuitBreakerFallbackFactory.java @@ -0,0 +1,97 @@ +/* + * 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.feign; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.tencent.polaris.api.pojo.CircuitBreakerStatus; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import feign.Request; +import feign.RequestTemplate; +import feign.Response; +import feign.codec.Decoder; + +import org.springframework.cloud.openfeign.FallbackFactory; + +/** + * PolarisCircuitBreakerFallbackFactory. + * + * @author sean yu + */ +public class PolarisCircuitBreakerFallbackFactory implements FallbackFactory { + + private final Decoder decoder; + + public PolarisCircuitBreakerFallbackFactory(Decoder decoder) { + this.decoder = decoder; + } + + @Override + public Object create(Throwable t) { + return new DefaultFallback(t, decoder); + } + + public class DefaultFallback { + + private final Throwable t; + + private final Decoder decoder; + + public DefaultFallback(Throwable t, Decoder decoder) { + this.t = t; + this.decoder = decoder; + } + + public Object fallback(Method method) { + if (t instanceof CallAbortedException) { + CircuitBreakerStatus.FallbackInfo fallbackInfo = ((CallAbortedException) t).getFallbackInfo(); + if (fallbackInfo != null) { + Response.Builder responseBuilder = Response.builder() + .status(fallbackInfo.getCode()); + if (fallbackInfo.getHeaders() != null) { + Map<String, Collection<String>> headers = new HashMap<>(); + fallbackInfo.getHeaders().forEach((k, v) -> headers.put(k, Collections.singleton(v))); + responseBuilder.headers(headers); + } + if (fallbackInfo.getBody() != null) { + responseBuilder.body(fallbackInfo.getBody(), StandardCharsets.UTF_8); + } + // Feign Response need a nonnull Request, + // which is not important in fallback response (no real request), + // so we create a fake one + Request fakeRequest = Request.create(Request.HttpMethod.GET, "/", new HashMap<>(), Request.Body.empty(), new RequestTemplate()); + responseBuilder.request(fakeRequest); + + try (Response response = responseBuilder.build()) { + return decoder.decode(response, method.getGenericReturnType()); + } + catch (IOException e) { + throw new IllegalStateException(e); + } + } + } + throw new IllegalStateException(t); + } + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisCircuitBreakerNameResolver.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisCircuitBreakerNameResolver.java new file mode 100644 index 000000000..e627ed09a --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisCircuitBreakerNameResolver.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.polaris.circuitbreaker.feign; + +import java.lang.reflect.Method; + +import com.tencent.cloud.common.metadata.MetadataContext; +import feign.Target; + +import org.springframework.web.bind.annotation.RequestMapping; + +import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation; + +/** + * PolarisCircuitBreakerNameResolver. + * + * @author seanyu 2023-02-27 + */ +public class PolarisCircuitBreakerNameResolver { + + public String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method) { + String serviceName = target.name(); + RequestMapping requestMapping = findMergedAnnotation(method, RequestMapping.class); + String path = ""; + if (requestMapping != null) { + path = requestMapping.path().length == 0 ? + requestMapping.value().length == 0 ? "" : requestMapping.value()[0] : + requestMapping.path()[0]; + } + return "".equals(path) ? + MetadataContext.LOCAL_NAMESPACE + "#" + serviceName : + MetadataContext.LOCAL_NAMESPACE + "#" + serviceName + "#" + path; + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignCircuitBreaker.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignCircuitBreaker.java new file mode 100644 index 000000000..65b7086a6 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignCircuitBreaker.java @@ -0,0 +1,99 @@ +/* + * 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.feign; + +import java.lang.reflect.Field; + +import feign.Feign; +import feign.Target; +import feign.codec.Decoder; + +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.cloud.openfeign.FeignCircuitBreaker; +import org.springframework.util.ReflectionUtils; + +/** + * PolarisFeignCircuitBreaker, mostly copy from {@link FeignCircuitBreaker}, but giving Polaris modification. + * + * @author sean yu + */ +public final class PolarisFeignCircuitBreaker { + + private PolarisFeignCircuitBreaker() { + throw new IllegalStateException("Don't instantiate a utility class"); + } + + /** + * @return builder for Feign CircuitBreaker integration + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for Feign CircuitBreaker integration. + */ + public static final class Builder extends Feign.Builder { + + private CircuitBreakerFactory circuitBreakerFactory; + private String feignClientName; + private PolarisCircuitBreakerNameResolver circuitBreakerNameResolver; + + public Builder() { + } + + public Builder circuitBreakerFactory(CircuitBreakerFactory circuitBreakerFactory) { + this.circuitBreakerFactory = circuitBreakerFactory; + return this; + } + + public Builder feignClientName(String feignClientName) { + this.feignClientName = feignClientName; + return this; + } + + public Builder circuitBreakerNameResolver(PolarisCircuitBreakerNameResolver circuitBreakerNameResolver) { + this.circuitBreakerNameResolver = circuitBreakerNameResolver; + return this; + } + + public <T> T target(Target<T> target, T fallback) { + return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null).newInstance(target); + } + + public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) { + return build(fallbackFactory).newInstance(target); + } + + @Override + public <T> T target(Target<T> target) { + return build(null).newInstance(target); + } + + public Feign build(final FallbackFactory<?> nullableFallbackFactory) { + Field field = ReflectionUtils.findField(Feign.Builder.class, "decoder"); + field.setAccessible(true); + Decoder decoder = (Decoder) ReflectionUtils.getField(field, this); + this.invocationHandlerFactory((target, dispatch) -> new PolarisFeignCircuitBreakerInvocationHandler( + circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory, circuitBreakerNameResolver, decoder)); + return this.build(); + } + + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignCircuitBreakerInvocationHandler.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignCircuitBreakerInvocationHandler.java new file mode 100644 index 000000000..18f7e8e61 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignCircuitBreakerInvocationHandler.java @@ -0,0 +1,199 @@ +/* + * 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.feign; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; + +import feign.InvocationHandlerFactory; +import feign.Target; +import feign.codec.Decoder; + +import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import static feign.Util.checkNotNull; + +/** + * PolarisFeignCircuitBreakerInvocationHandler, mostly copy from {@link org.springframework.cloud.openfeign.FeignCircuitBreakerInvocationHandler}, but giving Polaris modification. + * + * @author sean yu + */ +public class PolarisFeignCircuitBreakerInvocationHandler implements InvocationHandler { + + private final CircuitBreakerFactory factory; + + private final String feignClientName; + + private final Target<?> target; + + private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch; + + private final FallbackFactory<?> nullableFallbackFactory; + + private final Map<Method, Method> fallbackMethodMap; + + private final PolarisCircuitBreakerNameResolver circuitBreakerNameResolver; + + private final Decoder decoder; + + public PolarisFeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, String feignClientName, Target<?> target, + Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, FallbackFactory<?> nullableFallbackFactory, + PolarisCircuitBreakerNameResolver circuitBreakerNameResolver, Decoder decoder) { + this.factory = factory; + this.feignClientName = feignClientName; + this.target = checkNotNull(target, "target"); + this.dispatch = checkNotNull(dispatch, "dispatch"); + this.fallbackMethodMap = toFallbackMethod(dispatch); + this.nullableFallbackFactory = nullableFallbackFactory; + this.circuitBreakerNameResolver = circuitBreakerNameResolver; + this.decoder = decoder; + } + + /** + * If the method param of {@link InvocationHandler#invoke(Object, Method, Object[])} + * is not accessible, i.e in a package-private interface, the fallback call will cause + * of access restrictions. But methods in dispatch are copied methods. So setting + * access to dispatch method doesn't take effect to the method in + * InvocationHandler.invoke. Use map to store a copy of method to invoke the fallback + * to bypass this and reducing the count of reflection calls. + * @return cached methods map for fallback invoking + */ + static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) { + Map<Method, Method> result = new LinkedHashMap<>(); + for (Method method : dispatch.keySet()) { + method.setAccessible(true); + result.put(method, method); + } + return result; + } + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { + // early exit if the invoked method is from java.lang.Object + // code is the same as ReflectiveFeign.FeignInvocationHandler + if ("equals".equals(method.getName())) { + try { + Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; + return equals(otherHandler); + } + catch (IllegalArgumentException e) { + return false; + } + } + else if ("hashCode".equals(method.getName())) { + return hashCode(); + } + else if ("toString".equals(method.getName())) { + return toString(); + } + + String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method); + CircuitBreaker circuitBreaker = factory.create(circuitName); + Supplier<Object> supplier = asSupplier(method, args); + Function<Throwable, Object> fallbackFunction; + if (this.nullableFallbackFactory != null) { + fallbackFunction = throwable -> { + Object fallback = this.nullableFallbackFactory.create(throwable); + try { + return this.fallbackMethodMap.get(method).invoke(fallback, args); + } + catch (Exception exception) { + unwrapAndRethrow(exception); + } + return null; + }; + } + else { + fallbackFunction = throwable -> { + PolarisCircuitBreakerFallbackFactory.DefaultFallback fallback = + (PolarisCircuitBreakerFallbackFactory.DefaultFallback) new PolarisCircuitBreakerFallbackFactory(this.decoder).create(throwable); + return fallback.fallback(method); + }; + } + return circuitBreaker.run(supplier, fallbackFunction); + } + + private void unwrapAndRethrow(Exception exception) { + if (exception instanceof InvocationTargetException || exception instanceof NoFallbackAvailableException) { + Throwable underlyingException = exception.getCause(); + if (underlyingException instanceof RuntimeException) { + throw (RuntimeException) underlyingException; + } + if (underlyingException != null) { + throw new IllegalStateException(underlyingException); + } + throw new IllegalStateException(exception); + } + } + + private Supplier<Object> asSupplier(final Method method, final Object[] args) { + final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + final Thread caller = Thread.currentThread(); + return () -> { + boolean isAsync = caller != Thread.currentThread(); + try { + if (isAsync) { + RequestContextHolder.setRequestAttributes(requestAttributes); + } + return dispatch.get(method).invoke(args); + } + catch (RuntimeException throwable) { + throw throwable; + } + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + finally { + if (isAsync) { + RequestContextHolder.resetRequestAttributes(); + } + } + }; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PolarisFeignCircuitBreakerInvocationHandler) { + PolarisFeignCircuitBreakerInvocationHandler other = (PolarisFeignCircuitBreakerInvocationHandler) obj; + return this.target.equals(other.target); + } + return false; + } + + @Override + public int hashCode() { + return this.target.hashCode(); + } + + @Override + public String toString() { + return this.target.toString(); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/gateway/PolarisCircuitBreakerFilterFactory.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/gateway/PolarisCircuitBreakerFilterFactory.java new file mode 100644 index 000000000..0b474f8e5 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/gateway/PolarisCircuitBreakerFilterFactory.java @@ -0,0 +1,278 @@ +/* + * 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.gateway; + + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import com.tencent.polaris.api.pojo.CircuitBreakerStatus; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.beans.InvalidPropertyException; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker; +import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; +import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerFilterFactory; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.cloud.gateway.support.HttpStatusHolder; +import org.springframework.cloud.gateway.support.ServiceUnavailableException; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.reactive.DispatcherHandler; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.util.UriComponentsBuilder; + +import static java.util.Optional.ofNullable; +import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.reset; + +/** + * PolarisCircuitBreakerFilterFactory. + * mostly copy from SpringCloudCircuitBreakerFilterFactory, but create ReactiveCircuitBreaker per request to build method level CircuitBreaker. + * + * @author seanyu 2023-02-27 + */ +public class PolarisCircuitBreakerFilterFactory extends SpringCloudCircuitBreakerFilterFactory { + + private final ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory; + private final ObjectProvider<DispatcherHandler> dispatcherHandlerProvider; + private String routeIdPrefix; + // do not use this dispatcherHandler directly, use getDispatcherHandler() instead. + private volatile DispatcherHandler dispatcherHandler; + + public PolarisCircuitBreakerFilterFactory( + ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory, + ObjectProvider<DispatcherHandler> dispatcherHandlerProvider, + ReactiveDiscoveryClient discoveryClient, + DiscoveryLocatorProperties properties + ) { + super(reactiveCircuitBreakerFactory, dispatcherHandlerProvider); + this.reactiveCircuitBreakerFactory = reactiveCircuitBreakerFactory; + this.dispatcherHandlerProvider = dispatcherHandlerProvider; + if (discoveryClient != null && properties != null) { + if (StringUtils.hasText(properties.getRouteIdPrefix())) { + routeIdPrefix = properties.getRouteIdPrefix(); + } + else { + routeIdPrefix = discoveryClient.getClass().getSimpleName() + "_"; + } + } + } + + private void addExceptionDetails(Throwable t, ServerWebExchange exchange) { + ofNullable(t).ifPresent( + exception -> exchange.getAttributes().put(CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR, exception)); + } + + private DispatcherHandler getDispatcherHandler() { + if (dispatcherHandler == null) { + dispatcherHandler = dispatcherHandlerProvider.getIfAvailable(); + } + return dispatcherHandler; + } + + private String getCircuitBreakerId(Config config) { + if (!StringUtils.hasText(config.getName()) && StringUtils.hasText(config.getRouteId())) { + if (routeIdPrefix != null && config.getRouteId().startsWith(routeIdPrefix)) { + return config.getRouteId().replace(routeIdPrefix, ""); + } + return config.getRouteId(); + } + return config.getName(); + } + + private boolean isNumeric(String statusString) { + try { + Integer.parseInt(statusString); + return true; + } + catch (NumberFormatException e) { + return false; + } + } + + private List<HttpStatus> getSeriesStatus(String series) { + if (!Arrays.asList("1**", "2**", "3**", "4**", "5**").contains(series)) { + throw new InvalidPropertyException(Config.class, "statusCodes", "polaris circuit breaker status code can only be a numeric http status, or a http series pattern, e.g. [\"1**\",\"2**\",\"3**\",\"4**\",\"5**\"]"); + } + HttpStatus[] allHttpStatus = HttpStatus.values(); + if (series.startsWith("1")) { + return Arrays.stream(allHttpStatus).filter(HttpStatus::is1xxInformational).collect(Collectors.toList()); + } + else if (series.startsWith("2")) { + return Arrays.stream(allHttpStatus).filter(HttpStatus::is2xxSuccessful).collect(Collectors.toList()); + } + else if (series.startsWith("3")) { + return Arrays.stream(allHttpStatus).filter(HttpStatus::is3xxRedirection).collect(Collectors.toList()); + } + else if (series.startsWith("4")) { + return Arrays.stream(allHttpStatus).filter(HttpStatus::is4xxClientError).collect(Collectors.toList()); + } + else if (series.startsWith("5")) { + return Arrays.stream(allHttpStatus).filter(HttpStatus::is5xxServerError).collect(Collectors.toList()); + } + return Arrays.asList(allHttpStatus); + } + + private Set<HttpStatus> getDefaultStatus() { + return Arrays.stream(HttpStatus.values()) + .filter(HttpStatus::is5xxServerError) + .collect(Collectors.toSet()); + } + + @Override + public GatewayFilter apply(Config config) { + Set<HttpStatus> statuses = config.getStatusCodes().stream() + .flatMap(statusCode -> { + List<HttpStatus> httpStatuses = new ArrayList<>(); + if (isNumeric(statusCode)) { + httpStatuses.add(HttpStatusHolder.parse(statusCode).getHttpStatus()); + } + else { + httpStatuses.addAll(getSeriesStatus(statusCode)); + } + return httpStatuses.stream(); + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (CollectionUtils.isEmpty(statuses)) { + statuses.addAll(getDefaultStatus()); + } + String circuitBreakerId = getCircuitBreakerId(config); + return new GatewayFilter() { + @Override + public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { + Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); + String serviceName = circuitBreakerId; + if (route != null) { + serviceName = route.getUri().getHost(); + } + String path = exchange.getRequest().getPath().value(); + ReactiveCircuitBreaker cb = reactiveCircuitBreakerFactory.create(serviceName + "#" + path); + return cb.run( + chain.filter(exchange) + .doOnSuccess(v -> { + // throw CircuitBreakerStatusCodeException by default for all need checking status + // so polaris can report right error status + Set<HttpStatus> statusNeedToCheck = new HashSet<>(); + statusNeedToCheck.addAll(statuses); + statusNeedToCheck.addAll(getDefaultStatus()); + HttpStatus status = exchange.getResponse().getStatusCode(); + if (statusNeedToCheck.contains(status)) { + throw new CircuitBreakerStatusCodeException(status); + } + }), + t -> { + // pre-check CircuitBreakerStatusCodeException's status matches input status + if (t instanceof CircuitBreakerStatusCodeException) { + HttpStatus status = ((CircuitBreakerStatusCodeException) t).getStatusCode(); + // no need to fallback + if (!statuses.contains(status)) { + return Mono.error(t); + } + } + // do fallback + if (config.getFallbackUri() == null) { + // polaris checking + if (t instanceof CallAbortedException) { + CircuitBreakerStatus.FallbackInfo fallbackInfo = ((CallAbortedException) t).getFallbackInfo(); + if (fallbackInfo != null) { + ServerHttpResponse response = exchange.getResponse(); + response.setRawStatusCode(fallbackInfo.getCode()); + if (fallbackInfo.getHeaders() != null) { + fallbackInfo.getHeaders() + .forEach((k, v) -> response.getHeaders().add(k, v)); + } + DataBuffer bodyBuffer = null; + if (fallbackInfo.getBody() != null) { + byte[] bytes = fallbackInfo.getBody().getBytes(StandardCharsets.UTF_8); + bodyBuffer = response.bufferFactory().wrap(bytes); + } + return bodyBuffer != null ? response.writeWith(Flux.just(bodyBuffer)) : response.setComplete(); + } + } + return Mono.error(t); + } + exchange.getResponse().setStatusCode(null); + reset(exchange); + + // TODO: copied from RouteToRequestUrlFilter + URI uri = exchange.getRequest().getURI(); + // TODO: assume always? + boolean encoded = containsEncodedParts(uri); + URI requestUrl = UriComponentsBuilder.fromUri(uri).host(null).port(null) + .uri(config.getFallbackUri()).scheme(null).build(encoded).toUri(); + exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); + addExceptionDetails(t, exchange); + + // Reset the exchange + reset(exchange); + + ServerHttpRequest request = exchange.getRequest().mutate().uri(requestUrl).build(); + return getDispatcherHandler().handle(exchange.mutate().request(request).build()); + }) + .onErrorResume(t -> handleErrorWithoutFallback(t)); + } + + @Override + public String toString() { + return filterToStringCreator(PolarisCircuitBreakerFilterFactory.this) + .append("name", config.getName()).append("fallback", config.getFallbackUri()).toString(); + } + }; + + } + + @Override + protected Mono<Void> handleErrorWithoutFallback(Throwable t) { + if (t instanceof java.util.concurrent.TimeoutException) { + return Mono.error(new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, t.getMessage(), t)); + } + if (t instanceof CallAbortedException) { + return Mono.error(new ServiceUnavailableException()); + } + if (t instanceof CircuitBreakerStatusCodeException) { + return Mono.empty(); + } + return Mono.error(t); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reactor/PolarisCircuitBreakerFluxOperator.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reactor/PolarisCircuitBreakerFluxOperator.java new file mode 100644 index 000000000..faad71135 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reactor/PolarisCircuitBreakerFluxOperator.java @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.circuitbreaker.reactor; + +import com.tencent.polaris.circuitbreak.api.InvokeHandler; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.CoreSubscriber; +import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxOperator; +import reactor.core.publisher.Operators; + +/** + * FluxOperator for PolarisCircuitBreaker. + * + * @author seanyu 2023-02-27 + */ +public class PolarisCircuitBreakerFluxOperator<T> extends FluxOperator<T, T> { + + private static final Logger LOGGER = LoggerFactory.getLogger(PolarisCircuitBreakerFluxOperator.class); + + private final InvokeHandler invokeHandler; + + protected PolarisCircuitBreakerFluxOperator(Flux<? extends T> source, InvokeHandler invokeHandler) { + super(source); + this.invokeHandler = invokeHandler; + } + + @Override + public void subscribe(CoreSubscriber<? super T> actual) { + try { + invokeHandler.acquirePermission(); + source.subscribe(new PolarisCircuitBreakerReactorSubscriber<>(invokeHandler, actual, false)); + } + catch (CallAbortedException e) { + LOGGER.debug("ReactivePolarisCircuitBreaker CallAbortedException: {}", e.getMessage()); + Operators.error(actual, e); + } + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reactor/PolarisCircuitBreakerMonoOperator.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reactor/PolarisCircuitBreakerMonoOperator.java new file mode 100644 index 000000000..6f0a44fef --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reactor/PolarisCircuitBreakerMonoOperator.java @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.circuitbreaker.reactor; + +import com.tencent.polaris.circuitbreak.api.InvokeHandler; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.CoreSubscriber; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoOperator; +import reactor.core.publisher.Operators; + +/** + * MonoOperator for PolarisCircuitBreaker. + * + * @author seanyu 2023-02-27 + */ +public class PolarisCircuitBreakerMonoOperator<T> extends MonoOperator<T, T> { + + private static final Logger LOGGER = LoggerFactory.getLogger(PolarisCircuitBreakerMonoOperator.class); + + private final InvokeHandler invokeHandler; + + protected PolarisCircuitBreakerMonoOperator(Mono<? extends T> source, InvokeHandler invokeHandler) { + super(source); + this.invokeHandler = invokeHandler; + } + + @Override + public void subscribe(CoreSubscriber<? super T> actual) { + try { + invokeHandler.acquirePermission(); + source.subscribe(new PolarisCircuitBreakerReactorSubscriber<>(invokeHandler, actual, true)); + } + catch (CallAbortedException e) { + LOGGER.debug("ReactivePolarisCircuitBreaker CallAbortedException: {}", e.getMessage()); + Operators.error(actual, e); + } + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reactor/PolarisCircuitBreakerReactorSubscriber.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reactor/PolarisCircuitBreakerReactorSubscriber.java new file mode 100644 index 000000000..202f63369 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reactor/PolarisCircuitBreakerReactorSubscriber.java @@ -0,0 +1,119 @@ +/* + * 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.reactor; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.tencent.polaris.circuitbreak.api.InvokeHandler; +import com.tencent.polaris.circuitbreak.api.pojo.InvokeContext; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.publisher.BaseSubscriber; +import reactor.util.context.Context; + +/** + * Reactor Subscriber for PolarisCircuitBreaker. + * + * @author seanyu 2023-02-27 + */ +public class PolarisCircuitBreakerReactorSubscriber<T> extends BaseSubscriber<T> { + + private final InvokeHandler invokeHandler; + + + private final CoreSubscriber<? super T> downstreamSubscriber; + + private final long startTimeMilli; + private final boolean singleProducer; + + private final AtomicBoolean successSignaled = new AtomicBoolean(false); + private final AtomicBoolean eventWasEmitted = new AtomicBoolean(false); + + public PolarisCircuitBreakerReactorSubscriber(InvokeHandler invokeHandler, CoreSubscriber<? super T> downstreamSubscriber, boolean singleProducer) { + this.invokeHandler = invokeHandler; + this.downstreamSubscriber = downstreamSubscriber; + this.singleProducer = singleProducer; + this.startTimeMilli = System.currentTimeMillis(); + } + + @Override + public Context currentContext() { + return downstreamSubscriber.currentContext(); + } + + @Override + protected void hookOnSubscribe(Subscription subscription) { + downstreamSubscriber.onSubscribe(this); + } + + @Override + protected void hookOnNext(T value) { + if (!isDisposed()) { + if (singleProducer && successSignaled.compareAndSet(false, true)) { + long delay = System.currentTimeMillis() - startTimeMilli; + InvokeContext.ResponseContext responseContext = new InvokeContext.ResponseContext(); + responseContext.setDuration(delay); + responseContext.setDurationUnit(TimeUnit.MILLISECONDS); + responseContext.setResult(value); + invokeHandler.onSuccess(responseContext); + } + eventWasEmitted.set(true); + + downstreamSubscriber.onNext(value); + } + } + + @Override + protected void hookOnComplete() { + if (successSignaled.compareAndSet(false, true)) { + long delay = System.currentTimeMillis() - startTimeMilli; + InvokeContext.ResponseContext responseContext = new InvokeContext.ResponseContext(); + responseContext.setDuration(delay); + responseContext.setDurationUnit(TimeUnit.MILLISECONDS); + invokeHandler.onSuccess(responseContext); + } + + downstreamSubscriber.onComplete(); + } + + @Override + public void hookOnCancel() { + if (!successSignaled.get()) { + if (eventWasEmitted.get()) { + long delay = System.currentTimeMillis() - startTimeMilli; + InvokeContext.ResponseContext responseContext = new InvokeContext.ResponseContext(); + responseContext.setDuration(delay); + responseContext.setDurationUnit(TimeUnit.MILLISECONDS); + invokeHandler.onSuccess(responseContext); + } + } + } + + @Override + protected void hookOnError(Throwable e) { + long delay = System.currentTimeMillis() - startTimeMilli; + InvokeContext.ResponseContext responseContext = new InvokeContext.ResponseContext(); + responseContext.setDuration(delay); + responseContext.setDurationUnit(TimeUnit.MILLISECONDS); + responseContext.setError(e); + invokeHandler.onError(responseContext); + downstreamSubscriber.onError(e); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reactor/PolarisCircuitBreakerReactorTransformer.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reactor/PolarisCircuitBreakerReactorTransformer.java new file mode 100644 index 000000000..b7fa450e1 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reactor/PolarisCircuitBreakerReactorTransformer.java @@ -0,0 +1,53 @@ +/* + * 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.reactor; + +import java.util.function.Function; + +import com.tencent.polaris.circuitbreak.api.InvokeHandler; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * Reactor Transformer for PolarisCircuitBreaker. + * + * @author seanyu 2023-02-27 + */ +public class PolarisCircuitBreakerReactorTransformer<T> implements Function<Publisher<T>, Publisher<T>> { + + private final InvokeHandler invokeHandler; + + public PolarisCircuitBreakerReactorTransformer(InvokeHandler invokeHandler) { + this.invokeHandler = invokeHandler; + } + + @Override + public Publisher<T> apply(Publisher<T> publisher) { + if (publisher instanceof Mono) { + return new PolarisCircuitBreakerMonoOperator<>((Mono<? extends T>) publisher, invokeHandler); + } + else if (publisher instanceof Flux) { + return new PolarisCircuitBreakerFluxOperator<>((Flux<? extends T>) publisher, invokeHandler); + } + else { + throw new IllegalStateException("Publisher type is not supported: " + publisher.getClass().getCanonicalName()); + } + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reporter/ExceptionCircuitBreakerReporter.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reporter/ExceptionCircuitBreakerReporter.java new file mode 100644 index 000000000..b47c1a4a2 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reporter/ExceptionCircuitBreakerReporter.java @@ -0,0 +1,97 @@ +/* + * 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.reporter; + +import java.util.Optional; + +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; +import com.tencent.polaris.api.plugin.circuitbreaker.ResourceStat; +import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; +import com.tencent.polaris.client.api.SDKContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.core.Ordered; + +public class ExceptionCircuitBreakerReporter extends AbstractPolarisReporterAdapter implements EnhancedPlugin { + + private static final Logger LOG = LoggerFactory.getLogger(ExceptionCircuitBreakerReporter.class); + + private final CircuitBreakAPI circuitBreakAPI; + + public ExceptionCircuitBreakerReporter(RpcEnhancementReporterProperties reportProperties, + SDKContext context, + CircuitBreakAPI circuitBreakAPI) { + super(reportProperties, context); + this.circuitBreakAPI = circuitBreakAPI; + } + + @Override + public String getName() { + return ExceptionCircuitBreakerReporter.class.getName(); + } + + @Override + public EnhancedPluginType getType() { + return EnhancedPluginType.EXCEPTION; + } + + @Override + public void run(EnhancedPluginContext context) throws Throwable { + if (!super.reportProperties.isEnabled()) { + return; + } + + EnhancedRequestContext request = context.getRequest(); + ServiceInstance serviceInstance = Optional.ofNullable(context.getServiceInstance()).orElse(new DefaultServiceInstance()); + + ResourceStat resourceStat = createInstanceResourceStat( + serviceInstance.getServiceId(), + serviceInstance.getHost(), + serviceInstance.getPort(), + request.getUrl(), + null, + context.getDelay(), + context.getThrowable() + ); + + LOG.debug("Will report CircuitBreaker ResourceStat of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.", + resourceStat.getRetStatus().name(), request.getHttpMethod().name(), request.getUrl().getPath(), context.getThrowable().getMessage(), context.getDelay()); + + circuitBreakAPI.report(resourceStat); + + } + + @Override + public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) { + LOG.error("ExceptionCircuitBreakerReporter runs failed. context=[{}].", + context, throwable); + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE + 2; + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reporter/SuccessCircuitBreakerReporter.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reporter/SuccessCircuitBreakerReporter.java new file mode 100644 index 000000000..ce5fe2a81 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/reporter/SuccessCircuitBreakerReporter.java @@ -0,0 +1,99 @@ +/* + * 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.reporter; + +import java.util.Optional; + +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; +import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter; +import com.tencent.polaris.api.plugin.circuitbreaker.ResourceStat; +import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; +import com.tencent.polaris.client.api.SDKContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.core.Ordered; + + +public class SuccessCircuitBreakerReporter extends AbstractPolarisReporterAdapter implements EnhancedPlugin { + + private static final Logger LOG = LoggerFactory.getLogger(SuccessPolarisReporter.class); + + private final CircuitBreakAPI circuitBreakAPI; + + public SuccessCircuitBreakerReporter(RpcEnhancementReporterProperties reportProperties, + SDKContext context, + CircuitBreakAPI circuitBreakAPI) { + super(reportProperties, context); + this.circuitBreakAPI = circuitBreakAPI; + } + + @Override + public String getName() { + return SuccessCircuitBreakerReporter.class.getName(); + } + + @Override + public EnhancedPluginType getType() { + return EnhancedPluginType.POST; + } + + @Override + public void run(EnhancedPluginContext context) throws Throwable { + if (!super.reportProperties.isEnabled()) { + return; + } + EnhancedRequestContext request = context.getRequest(); + EnhancedResponseContext response = context.getResponse(); + ServiceInstance serviceInstance = Optional.ofNullable(context.getServiceInstance()).orElse(new DefaultServiceInstance()); + + ResourceStat resourceStat = createInstanceResourceStat( + serviceInstance.getServiceId(), + serviceInstance.getHost(), + serviceInstance.getPort(), + request.getUrl(), + response.getHttpStatus(), + context.getDelay(), + null + ); + + LOG.debug("Will report CircuitBreaker ResourceStat of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.", + resourceStat.getRetStatus().name(), request.getHttpMethod().name(), request.getUrl().getPath(), response.getHttpStatus(), context.getDelay()); + + circuitBreakAPI.report(resourceStat); + } + + @Override + public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) { + LOG.error("SuccessCircuitBreakerReporter runs failed. context=[{}].", + context, throwable); + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE + 2; + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreaker.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreaker.java new file mode 100644 index 000000000..d3a8af492 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreaker.java @@ -0,0 +1,54 @@ +/* + * 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.resttemplate; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * PolarisCircuitBreaker annotation. + * if coded fallback or fallbackClass provided, RestTemplate will always return fallback when any exception occurs, + * if none coded fallback or fallbackClass provided, RestTemplate will return fallback response from Polaris server when fallback occurs. + * fallback and fallbackClass cannot provide at same time. + * + * @author sean yu + */ +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface PolarisCircuitBreaker { + + /** + * a fallback string, will return a response { status: 200, body: fallback string} when any exception occurs. + * + * @return fallback string + */ + String fallback() default ""; + + /** + * a fallback Class, will return a PolarisCircuitBreakerHttpResponse when any exception occurs. + * fallback Class must be a spring bean. + * + * @return PolarisCircuitBreakerFallback + */ + Class<? extends PolarisCircuitBreakerFallback> fallbackClass() default PolarisCircuitBreakerFallback.class; + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreakerFallback.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreakerFallback.java new file mode 100644 index 000000000..9c59795c9 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreakerFallback.java @@ -0,0 +1,29 @@ +/* + * 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.resttemplate; + +/** + * PolarisCircuitBreakerFallback. + * + * @author sean yu + */ +public interface PolarisCircuitBreakerFallback { + + PolarisCircuitBreakerHttpResponse fallback(); + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreakerHttpResponse.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreakerHttpResponse.java new file mode 100644 index 000000000..0aeade4d5 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreakerHttpResponse.java @@ -0,0 +1,102 @@ +/* + * 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.resttemplate; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import com.tencent.polaris.api.pojo.CircuitBreakerStatus; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.AbstractClientHttpResponse; + +/** + * PolarisCircuitBreakerHttpResponse. + * + * @author sean yu + */ +public class PolarisCircuitBreakerHttpResponse extends AbstractClientHttpResponse { + + private final CircuitBreakerStatus.FallbackInfo fallbackInfo; + + private final HttpHeaders headers = new HttpHeaders(); + + private InputStream body; + + public PolarisCircuitBreakerHttpResponse(int code) { + this(new CircuitBreakerStatus.FallbackInfo(code, null, null)); + } + + public PolarisCircuitBreakerHttpResponse(int code, String body) { + this(new CircuitBreakerStatus.FallbackInfo(code, null, body)); + } + + public PolarisCircuitBreakerHttpResponse(int code, Map<String, String> headers, String body) { + this(new CircuitBreakerStatus.FallbackInfo(code, headers, body)); + } + + public PolarisCircuitBreakerHttpResponse(CircuitBreakerStatus.FallbackInfo fallbackInfo) { + this.fallbackInfo = fallbackInfo; + if (fallbackInfo.getHeaders() != null) { + fallbackInfo.getHeaders().forEach(headers::add); + } + if (fallbackInfo.getBody() != null) { + body = new ByteArrayInputStream(fallbackInfo.getBody().getBytes()); + } + } + + @Override + public final int getRawStatusCode() { + return fallbackInfo.getCode(); + } + + @Override + public final String getStatusText() { + HttpStatus status = HttpStatus.resolve(getRawStatusCode()); + return (status != null ? status.getReasonPhrase() : ""); + } + + @Override + public final void close() { + if (this.body != null) { + try { + this.body.close(); + } + catch (IOException e) { + // Ignore exception on close... + } + } + } + + @Override + public final InputStream getBody() { + return this.body; + } + + @Override + public final HttpHeaders getHeaders() { + return this.headers; + } + + public CircuitBreakerStatus.FallbackInfo getFallbackInfo() { + return this.fallbackInfo; + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreakerRestTemplateBeanPostProcessor.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreakerRestTemplateBeanPostProcessor.java new file mode 100644 index 000000000..775a6cfa6 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreakerRestTemplateBeanPostProcessor.java @@ -0,0 +1,125 @@ +/* + * 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.resttemplate; + +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.core.type.MethodMetadata; +import org.springframework.core.type.StandardMethodMetadata; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +/** + * PolarisCircuitBreakerRestTemplateBeanPostProcessor. + * + * @author sean yu + */ +public class PolarisCircuitBreakerRestTemplateBeanPostProcessor implements MergedBeanDefinitionPostProcessor { + + private final ApplicationContext applicationContext; + + public PolarisCircuitBreakerRestTemplateBeanPostProcessor(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + private final ConcurrentHashMap<String, PolarisCircuitBreaker> cache = new ConcurrentHashMap<>(); + + private void checkPolarisCircuitBreakerRestTemplate(PolarisCircuitBreaker polarisCircuitBreaker) { + if ( + StringUtils.hasText(polarisCircuitBreaker.fallback()) && + !PolarisCircuitBreakerFallback.class.toGenericString().equals(polarisCircuitBreaker.fallbackClass().toGenericString()) + ) { + throw new IllegalArgumentException("PolarisCircuitBreaker's fallback and fallbackClass could not set at sametime !"); + } + } + + @Override + public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { + if (checkAnnotated(beanDefinition, beanType, beanName)) { + PolarisCircuitBreaker polarisCircuitBreaker; + if (beanDefinition.getSource() instanceof StandardMethodMetadata) { + polarisCircuitBreaker = ((StandardMethodMetadata) beanDefinition.getSource()).getIntrospectedMethod() + .getAnnotation(PolarisCircuitBreaker.class); + } + else { + polarisCircuitBreaker = beanDefinition.getResolvedFactoryMethod() + .getAnnotation(PolarisCircuitBreaker.class); + } + checkPolarisCircuitBreakerRestTemplate(polarisCircuitBreaker); + cache.put(beanName, polarisCircuitBreaker); + } + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (cache.containsKey(beanName)) { + // add interceptor for each RestTemplate with @PolarisCircuitBreaker annotation + StringBuilder interceptorBeanNamePrefix = new StringBuilder(); + PolarisCircuitBreaker polarisCircuitBreaker = cache.get(beanName); + interceptorBeanNamePrefix + .append(StringUtils.uncapitalize( + PolarisCircuitBreaker.class.getSimpleName())) + .append("_") + .append(polarisCircuitBreaker.fallback()) + .append("_") + .append(polarisCircuitBreaker.fallbackClass().getSimpleName()); + RestTemplate restTemplate = (RestTemplate) bean; + String interceptorBeanName = interceptorBeanNamePrefix + "@" + bean; + CircuitBreakerFactory circuitBreakerFactory = this.applicationContext.getBean(CircuitBreakerFactory.class); + registerBean(interceptorBeanName, polarisCircuitBreaker, applicationContext, circuitBreakerFactory, restTemplate); + PolarisCircuitBreakerRestTemplateInterceptor polarisCircuitBreakerRestTemplateInterceptor = applicationContext + .getBean(interceptorBeanName, PolarisCircuitBreakerRestTemplateInterceptor.class); + restTemplate.getInterceptors().add(0, polarisCircuitBreakerRestTemplateInterceptor); + } + return bean; + } + + private boolean checkAnnotated(RootBeanDefinition beanDefinition, + Class<?> beanType, String beanName) { + return beanName != null && beanType == RestTemplate.class + && beanDefinition.getSource() instanceof MethodMetadata + && ((MethodMetadata) beanDefinition.getSource()) + .isAnnotated(PolarisCircuitBreaker.class.getName()); + } + + private void registerBean(String interceptorBeanName, PolarisCircuitBreaker polarisCircuitBreaker, + ApplicationContext applicationContext, CircuitBreakerFactory circuitBreakerFactory, RestTemplate restTemplate) { + // register PolarisCircuitBreakerRestTemplateInterceptor bean + DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext + .getAutowireCapableBeanFactory(); + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .genericBeanDefinition(PolarisCircuitBreakerRestTemplateInterceptor.class); + beanDefinitionBuilder.addConstructorArgValue(polarisCircuitBreaker); + beanDefinitionBuilder.addConstructorArgValue(applicationContext); + beanDefinitionBuilder.addConstructorArgValue(circuitBreakerFactory); + beanDefinitionBuilder.addConstructorArgValue(restTemplate); + BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder + .getRawBeanDefinition(); + beanFactory.registerBeanDefinition(interceptorBeanName, + interceptorBeanDefinition); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreakerRestTemplateInterceptor.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreakerRestTemplateInterceptor.java new file mode 100644 index 000000000..49afb2210 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisCircuitBreakerRestTemplateInterceptor.java @@ -0,0 +1,101 @@ +/* + * 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.resttemplate; + +import java.io.IOException; +import java.lang.reflect.Method; + +import com.tencent.polaris.api.pojo.CircuitBreakerStatus; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; + +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +/** + * PolarisCircuitBreakerRestTemplateInterceptor. + * + * @author sean yu + */ +public class PolarisCircuitBreakerRestTemplateInterceptor implements ClientHttpRequestInterceptor { + + private final PolarisCircuitBreaker polarisCircuitBreaker; + + private final ApplicationContext applicationContext; + + private final CircuitBreakerFactory circuitBreakerFactory; + + private final RestTemplate restTemplate; + + public PolarisCircuitBreakerRestTemplateInterceptor( + PolarisCircuitBreaker polarisCircuitBreaker, + ApplicationContext applicationContext, + CircuitBreakerFactory circuitBreakerFactory, + RestTemplate restTemplate + ) { + this.polarisCircuitBreaker = polarisCircuitBreaker; + this.applicationContext = applicationContext; + this.circuitBreakerFactory = circuitBreakerFactory; + this.restTemplate = restTemplate; + } + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + return circuitBreakerFactory.create(request.getURI().getHost() + "#" + request.getURI().getPath()).run( + () -> { + try { + ClientHttpResponse response = execution.execute(request, body); + ResponseErrorHandler errorHandler = restTemplate.getErrorHandler(); + if (errorHandler.hasError(response)) { + errorHandler.handleError(request.getURI(), request.getMethod(), response); + } + return response; + } + catch (IOException e) { + throw new IllegalStateException(e); + } + }, + t -> { + if (StringUtils.hasText(polarisCircuitBreaker.fallback())) { + CircuitBreakerStatus.FallbackInfo fallbackInfo = new CircuitBreakerStatus.FallbackInfo(200, null, polarisCircuitBreaker.fallback()); + return new PolarisCircuitBreakerHttpResponse(fallbackInfo); + } + if (!PolarisCircuitBreakerFallback.class.toGenericString().equals(polarisCircuitBreaker.fallbackClass().toGenericString())) { + Method method = ReflectionUtils.findMethod(PolarisCircuitBreakerFallback.class, "fallback"); + PolarisCircuitBreakerFallback polarisCircuitBreakerFallback = applicationContext.getBean(polarisCircuitBreaker.fallbackClass()); + return (PolarisCircuitBreakerHttpResponse) ReflectionUtils.invokeMethod(method, polarisCircuitBreakerFallback); + } + if (t instanceof CallAbortedException) { + CircuitBreakerStatus.FallbackInfo fallbackInfo = ((CallAbortedException) t).getFallbackInfo(); + if (fallbackInfo != null) { + return new PolarisCircuitBreakerHttpResponse(fallbackInfo); + } + } + throw new IllegalStateException(t); + } + ); + } + +} 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 new file mode 100644 index 000000000..961c30802 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/util/PolarisCircuitBreakerUtils.java @@ -0,0 +1,87 @@ +/* + * 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.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; + +/** + * PolarisCircuitBreakerUtils. + * + * @author seanyu 2023-02-27 + */ +public final class PolarisCircuitBreakerUtils { + + private static final Logger LOG = LoggerFactory.getLogger(PolarisCircuitBreakerUtils.class); + + private PolarisCircuitBreakerUtils() { + + } + + /** + * + * @param id CircuitBreakerId + * Format: namespace#service#method or service#method or service , + * namespace set as default spring.cloud.polaris.namespace if absent + * @return String[]{namespace, service, method} + */ + public static String[] resolveCircuitBreakerId(String id) { + Assert.hasText(id, "A CircuitBreaker must have an id. Id could be : namespace#service#method or service#method or service"); + String[] polarisCircuitBreakerMetaData = id.split("#"); + if (polarisCircuitBreakerMetaData.length == 2) { + return new String[]{MetadataContext.LOCAL_NAMESPACE, polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1]}; + } + if (polarisCircuitBreakerMetaData.length == 3) { + return new String[]{polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1], polarisCircuitBreakerMetaData[2]}; + } + return new String[]{MetadataContext.LOCAL_NAMESPACE, id, ""}; + } + + public static void reportStatus(ConsumerAPI consumerAPI, + PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, CallAbortedException e) { + try { + ServiceCallResult result = new ServiceCallResult(); + result.setMethod(conf.getMethod()); + result.setNamespace(conf.getNamespace()); + result.setService(conf.getService()); + result.setRuleName(e.getRuleName()); + result.setRetStatus(RetStatus.RetReject); + result.setCallerService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService())); + + if (Objects.nonNull(e.getFallbackInfo())) { + result.setRetCode(e.getFallbackInfo().getCode()); + } + consumerAPI.updateServiceCallResult(result); + } + catch (Throwable ex) { + LOG.error("[CircuitBreaker] report circuitbreaker call result fail ", ex); + } + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/PolarisCircuitBreakerPostZuulFilter.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/PolarisCircuitBreakerPostZuulFilter.java new file mode 100644 index 000000000..ac6db8536 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/PolarisCircuitBreakerPostZuulFilter.java @@ -0,0 +1,152 @@ +/* + * 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.zuul; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.servlet.http.HttpServletResponse; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.exception.ZuulException; +import com.tencent.cloud.common.util.ZuulFilterUtils; +import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreaker; +import com.tencent.polaris.circuitbreak.api.pojo.InvokeContext; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.client.HttpStatusCodeException; + +import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_CIRCUIT_BREAKER; +import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_PRE_ROUTE_TIME; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SEND_RESPONSE_FILTER_ORDER; + +/** + * Polaris circuit breaker post-processing. Including reporting. + * + * @author Haotian Zhang + */ +public class PolarisCircuitBreakerPostZuulFilter extends ZuulFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(PolarisCircuitBreakerPostZuulFilter.class); + + private final PolarisZuulFallbackFactory polarisZuulFallbackFactory; + + private final Environment environment; + + public PolarisCircuitBreakerPostZuulFilter(PolarisZuulFallbackFactory polarisZuulFallbackFactory, + Environment environment) { + this.polarisZuulFallbackFactory = polarisZuulFallbackFactory; + this.environment = environment; + } + + @Override + public String filterType() { + return POST_TYPE; + } + + @Override + public int filterOrder() { + return SEND_RESPONSE_FILTER_ORDER - 1; + } + + @Override + public boolean shouldFilter() { + String enabled = environment.getProperty("spring.cloud.polaris.circuitbreaker.enabled"); + return StringUtils.isEmpty(enabled) || enabled.equals("true"); + } + + @Override + public Object run() throws ZuulException { + RequestContext context = RequestContext.getCurrentContext(); + + HttpServletResponse response = context.getResponse(); + HttpStatus status = HttpStatus.resolve(response.getStatus()); + + Object polarisCircuitBreakerObject = context.get(POLARIS_CIRCUIT_BREAKER); + Object startTimeMilliObject = context.get(POLARIS_PRE_ROUTE_TIME); + if (polarisCircuitBreakerObject != null && polarisCircuitBreakerObject instanceof PolarisCircuitBreaker + && startTimeMilliObject != null && startTimeMilliObject instanceof Long) { + PolarisCircuitBreaker polarisCircuitBreaker = (PolarisCircuitBreaker) polarisCircuitBreakerObject; + Long startTimeMilli = (Long) startTimeMilliObject; + long delay = System.currentTimeMillis() - startTimeMilli; + InvokeContext.ResponseContext responseContext = new InvokeContext.ResponseContext(); + responseContext.setDuration(delay); + responseContext.setDurationUnit(TimeUnit.MILLISECONDS); + + if (status != null && status.is5xxServerError()) { + Throwable throwable = new CircuitBreakerStatusCodeException(status); + responseContext.setError(throwable); + + // fallback if FallbackProvider is implemented. + String serviceId = ZuulFilterUtils.getServiceId(context); + FallbackProvider fallbackProvider = polarisZuulFallbackFactory.getFallbackProvider(serviceId); + if (fallbackProvider != null) { + ClientHttpResponse clientHttpResponse = fallbackProvider.fallbackResponse(serviceId, throwable); + try { + // set status code + context.setResponseStatusCode(clientHttpResponse.getRawStatusCode()); + // set headers + HttpHeaders headers = clientHttpResponse.getHeaders(); + for (String key : headers.keySet()) { + List<String> values = headers.get(key); + if (!CollectionUtils.isEmpty(values)) { + for (String value : values) { + context.addZuulResponseHeader(key, value); + } + } + } + // set body + context.getResponse().setCharacterEncoding("UTF-8"); + context.setResponseBody(IOUtils.toString(clientHttpResponse.getBody(), StandardCharsets.UTF_8)); + } + catch (Exception e) { + LOGGER.error("error filter exception", e); + } + } + } + + if (responseContext.getError() == null) { + polarisCircuitBreaker.onSuccess(responseContext); + } + else { + polarisCircuitBreaker.onError(responseContext); + } + } + return null; + } + + public class CircuitBreakerStatusCodeException extends HttpStatusCodeException { + + public CircuitBreakerStatusCodeException(HttpStatus statusCode) { + super(statusCode); + } + + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/PolarisCircuitBreakerZuulFilter.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/PolarisCircuitBreakerZuulFilter.java new file mode 100644 index 000000000..ce09b2db5 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/PolarisCircuitBreakerZuulFilter.java @@ -0,0 +1,154 @@ +/* + * 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.zuul; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.exception.ZuulException; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.ZuulFilterUtils; +import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreaker; +import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerHttpResponse; +import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_CIRCUIT_BREAKER; +import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_IS_IN_ROUTING_STATE; +import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_PRE_ROUTE_TIME; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE; + +/** + * Polaris circuit breaker implement in Zuul. + * + * @author Haotian Zhang + */ +public class PolarisCircuitBreakerZuulFilter extends ZuulFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(PolarisCircuitBreakerZuulFilter.class); + + private final CircuitBreakerFactory circuitBreakerFactory; + + private final PolarisZuulFallbackFactory polarisZuulFallbackFactory; + + private final Environment environment; + + public PolarisCircuitBreakerZuulFilter( + CircuitBreakerFactory circuitBreakerFactory, + PolarisZuulFallbackFactory polarisZuulFallbackFactory, + Environment environment) { + this.circuitBreakerFactory = circuitBreakerFactory; + this.polarisZuulFallbackFactory = polarisZuulFallbackFactory; + this.environment = environment; + } + + @Override + public String filterType() { + return PRE_TYPE; + } + + /** + * ServiceId is set after PreDecorationFilter. + * + * @return filter order + */ + @Override + public int filterOrder() { + return PRE_DECORATION_FILTER_ORDER + 2; + } + + @Override + public boolean shouldFilter() { + String enabled = environment.getProperty("spring.cloud.polaris.circuitbreaker.enabled"); + return StringUtils.isEmpty(enabled) || enabled.equals("true"); + } + + @Override + public Object run() throws ZuulException { + RequestContext context = RequestContext.getCurrentContext(); + String serviceId = ZuulFilterUtils.getServiceId(context); + String path = ZuulFilterUtils.getPath(context); + String circuitName = "".equals(path) ? + MetadataContext.LOCAL_NAMESPACE + "#" + serviceId : + MetadataContext.LOCAL_NAMESPACE + "#" + serviceId + "#" + path; + CircuitBreaker circuitBreaker = circuitBreakerFactory.create(circuitName); + if (circuitBreaker instanceof PolarisCircuitBreaker) { + PolarisCircuitBreaker polarisCircuitBreaker = (PolarisCircuitBreaker) circuitBreaker; + context.set(POLARIS_CIRCUIT_BREAKER, circuitBreaker); + try { + polarisCircuitBreaker.acquirePermission(); + } + catch (CallAbortedException exception) { + FallbackProvider fallbackProvider = polarisZuulFallbackFactory.getFallbackProvider(serviceId); + ClientHttpResponse clientHttpResponse; + if (fallbackProvider != null) { + clientHttpResponse = fallbackProvider.fallbackResponse(serviceId, exception); + } + else if (exception.getFallbackInfo() != null) { + clientHttpResponse = new PolarisCircuitBreakerHttpResponse(exception.getFallbackInfo()); + } + else { + throw new IllegalStateException(exception); + } + try { + context.setSendZuulResponse(false); + // set status code + context.setResponseStatusCode(clientHttpResponse.getRawStatusCode()); + // set headers + HttpHeaders headers = clientHttpResponse.getHeaders(); + for (String key : headers.keySet()) { + List<String> values = headers.get(key); + if (!CollectionUtils.isEmpty(values)) { + for (String value : values) { + context.addZuulResponseHeader(key, value); + } + } + } + // set body + context.getResponse().setCharacterEncoding("UTF-8"); + context.setResponseBody(IOUtils.toString(clientHttpResponse.getBody(), StandardCharsets.UTF_8)); + LOGGER.debug("PolarisCircuitBreaker CallAbortedException: {}", exception.getMessage()); + PolarisCircuitBreakerUtils.reportStatus(polarisCircuitBreaker.getConsumerAPI(), polarisCircuitBreaker.getConf(), exception); + } + catch (IOException e) { + LOGGER.error("Return circuit breaker fallback info failed: {}", e.getMessage()); + throw new IllegalStateException(e); + } + } + context.set(POLARIS_PRE_ROUTE_TIME, Long.valueOf(System.currentTimeMillis())); + context.set(POLARIS_IS_IN_ROUTING_STATE, true); + } + return null; + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/PolarisZuulFallbackFactory.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/PolarisZuulFallbackFactory.java new file mode 100644 index 000000000..cdf1d3c4b --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/PolarisZuulFallbackFactory.java @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.circuitbreaker.zuul; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; + +/** + * Zuul fallback factory. + * + * @author Haotian Zhang + */ +public class PolarisZuulFallbackFactory { + private final Map<String, FallbackProvider> fallbackProviderCache; + + private FallbackProvider defaultFallbackProvider = null; + + public PolarisZuulFallbackFactory(Set<FallbackProvider> fallbackProviders) { + this.fallbackProviderCache = new HashMap<>(); + for (FallbackProvider provider : fallbackProviders) { + String route = provider.getRoute(); + if ("*".equals(route) || route == null) { + defaultFallbackProvider = provider; + } + else { + fallbackProviderCache.put(route, provider); + } + } + } + + public FallbackProvider getFallbackProvider(String route) { + FallbackProvider provider = fallbackProviderCache.get(route); + if (provider == null) { + provider = defaultFallbackProvider; + } + return provider; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/org/springframework/cloud/openfeign/PolarisFeignCircuitBreakerTargeter.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/org/springframework/cloud/openfeign/PolarisFeignCircuitBreakerTargeter.java new file mode 100644 index 000000000..9ac2ad59b --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/org/springframework/cloud/openfeign/PolarisFeignCircuitBreakerTargeter.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 org.springframework.cloud.openfeign; + +import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisCircuitBreakerNameResolver; +import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignCircuitBreaker; +import feign.Feign; +import feign.Target; + +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.util.StringUtils; + +/** + * PolarisFeignCircuitBreakerTargeter, mostly copy from {@link org.springframework.cloud.openfeign.FeignCircuitBreakerTargeter}, but giving Polaris modification. + * + * @author sean yu + */ +public class PolarisFeignCircuitBreakerTargeter implements Targeter { + + private final CircuitBreakerFactory circuitBreakerFactory; + + private final PolarisCircuitBreakerNameResolver circuitBreakerNameResolver; + + public PolarisFeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, PolarisCircuitBreakerNameResolver circuitBreakerNameResolver) { + this.circuitBreakerFactory = circuitBreakerFactory; + this.circuitBreakerNameResolver = circuitBreakerNameResolver; + } + + @Override + public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, + Target.HardCodedTarget<T> target) { + if (!(feign instanceof PolarisFeignCircuitBreaker.Builder)) { + return feign.target(target); + } + PolarisFeignCircuitBreaker.Builder builder = (PolarisFeignCircuitBreaker.Builder) feign; + String name = !StringUtils.hasText(factory.getContextId()) ? factory.getName() : factory.getContextId(); + Class<?> fallback = factory.getFallback(); + if (fallback != void.class) { + return targetWithFallback(name, context, target, builder, fallback); + } + Class<?> fallbackFactory = factory.getFallbackFactory(); + if (fallbackFactory != void.class) { + return targetWithFallbackFactory(name, context, target, builder, fallbackFactory); + } + return builder(name, builder).target(target); + } + + private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context, + Target.HardCodedTarget<T> target, PolarisFeignCircuitBreaker.Builder builder, Class<?> fallbackFactoryClass) { + FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext("fallbackFactory", + feignClientName, context, fallbackFactoryClass, FallbackFactory.class); + return builder(feignClientName, builder).target(target, fallbackFactory); + } + + private <T> T targetWithFallback(String feignClientName, FeignContext context, Target.HardCodedTarget<T> target, + PolarisFeignCircuitBreaker.Builder builder, Class<?> fallback) { + T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type()); + return builder(feignClientName, builder).target(target, fallbackInstance); + } + + private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context, + Class<?> beanType, Class<T> targetType) { + Object fallbackInstance = context.getInstance(feignClientName, beanType); + if (fallbackInstance == null) { + throw new IllegalStateException( + String.format("No " + fallbackMechanism + " instance of type %s found for feign client %s", + beanType, feignClientName)); + } + + if (!targetType.isAssignableFrom(beanType)) { + throw new IllegalStateException(String.format("Incompatible " + fallbackMechanism + + " instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s", + beanType, targetType, feignClientName)); + } + return (T) fallbackInstance; + } + + private PolarisFeignCircuitBreaker.Builder builder(String feignClientName, PolarisFeignCircuitBreaker.Builder builder) { + return builder + .circuitBreakerFactory(circuitBreakerFactory) + .feignClientName(feignClientName) + .circuitBreakerNameResolver(circuitBreakerNameResolver); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/org/springframework/cloud/openfeign/PolarisFeignCircuitBreakerTargeterAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/org/springframework/cloud/openfeign/PolarisFeignCircuitBreakerTargeterAutoConfiguration.java new file mode 100644 index 000000000..63b25fe2c --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/org/springframework/cloud/openfeign/PolarisFeignCircuitBreakerTargeterAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * 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 org.springframework.cloud.openfeign; + +import com.tencent.cloud.polaris.circuitbreaker.config.ConditionalOnPolarisCircuitBreakerEnabled; +import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisCircuitBreakerNameResolver; + +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** + * Auto configuration for PolarisFeignCircuitBreakerTargeter. + * + * @author Haotian Zhang + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({Targeter.class}) +@ConditionalOnProperty(value = "feign.circuitbreaker.enabled", havingValue = "true") +@AutoConfigureBefore(FeignAutoConfiguration.class) +@ConditionalOnPolarisCircuitBreakerEnabled +public class PolarisFeignCircuitBreakerTargeterAutoConfiguration { + + @Bean + @Primary + @ConditionalOnBean(CircuitBreakerFactory.class) + public Targeter polarisFeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, PolarisCircuitBreakerNameResolver circuitBreakerNameResolver) { + return new PolarisFeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerNameResolver); + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories index 418da24ab..41da60a9e 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories @@ -1,4 +1,8 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration + com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration,\ + com.tencent.cloud.polaris.circuitbreaker.config.ReactivePolarisCircuitBreakerAutoConfiguration,\ + com.tencent.cloud.polaris.circuitbreaker.config.GatewayPolarisCircuitBreakerAutoConfiguration,\ + com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration,\ + org.springframework.cloud.openfeign.PolarisFeignCircuitBreakerTargeterAutoConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerBootstrapConfiguration diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfigurationTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/CircuitBreakerPolarisAutoConfigurationTest.java similarity index 50% rename from spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfigurationTest.java rename to spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/CircuitBreakerPolarisAutoConfigurationTest.java index f1dcf17a7..5b05ab5ad 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/CircuitBreakerPolarisAutoConfigurationTest.java @@ -15,14 +15,21 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.polaris.circuitbreaker.config; +package com.tencent.cloud.polaris.circuitbreaker; +import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier; +import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration; +import com.tencent.cloud.polaris.circuitbreaker.config.ReactivePolarisCircuitBreakerAutoConfiguration; +import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisCircuitBreakerNameResolver; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; +import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; import static org.assertj.core.api.Assertions.assertThat; @@ -32,21 +39,42 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Haotian Zhang */ -public class PolarisCircuitBreakerAutoConfigurationTest { +public class CircuitBreakerPolarisAutoConfigurationTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of( PolarisContextAutoConfiguration.class, RpcEnhancementAutoConfiguration.class, LoadBalancerAutoConfiguration.class, - RpcEnhancementAutoConfiguration.class, PolarisCircuitBreakerAutoConfiguration.class)) .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true"); + private final ApplicationContextRunner reactiveContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + PolarisContextAutoConfiguration.class, + RpcEnhancementAutoConfiguration.class, + LoadBalancerAutoConfiguration.class, + ReactivePolarisCircuitBreakerAutoConfiguration.class)) + .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true"); + @Test public void testDefaultInitialization() { this.contextRunner.run(context -> { - assertThat(context).hasSingleBean( - PolarisCircuitBreakerAutoConfiguration.CircuitBreakerConfigModifier.class); + assertThat(context).hasSingleBean(PolarisCircuitBreakerAutoConfiguration.class); + assertThat(context).hasSingleBean(CircuitBreakerFactory.class); + assertThat(context).hasSingleBean(CircuitBreakerConfigModifier.class); + assertThat(context).hasSingleBean(CircuitBreakAPI.class); + assertThat(context).hasSingleBean(PolarisCircuitBreakerNameResolver.class); + }); + } + + @Test + public void testReactiveInitialization() { + this.reactiveContextRunner.run(context -> { + assertThat(context).hasSingleBean(ReactivePolarisCircuitBreakerAutoConfiguration.class); + assertThat(context).hasSingleBean(ReactiveCircuitBreakerFactory.class); + assertThat(context).hasSingleBean(CircuitBreakerConfigModifier.class); + assertThat(context).hasSingleBean(CircuitBreakAPI.class); }); } + } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfigurationTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfigurationTest.java similarity index 69% rename from spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfigurationTest.java rename to spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfigurationTest.java index 9213272cd..c78784308 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfigurationTest.java @@ -15,8 +15,12 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.polaris.circuitbreaker.config; +package com.tencent.cloud.polaris.circuitbreaker; +import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration; +import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerBootstrapConfiguration; +import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration; +import com.tencent.cloud.polaris.circuitbreaker.config.ReactivePolarisCircuitBreakerAutoConfiguration; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; import org.junit.jupiter.api.Test; @@ -34,10 +38,10 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class PolarisCircuitBreakerBootstrapConfigurationTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(PolarisContextAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of( + PolarisContextAutoConfiguration.class, RpcEnhancementAutoConfiguration.class, LoadBalancerAutoConfiguration.class, - RpcEnhancementAutoConfiguration.class, PolarisCircuitBreakerBootstrapConfiguration.class)) .withPropertyValues("spring.cloud.polaris.enabled=true") .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true"); @@ -45,8 +49,9 @@ public class PolarisCircuitBreakerBootstrapConfigurationTest { @Test public void testDefaultInitialization() { this.contextRunner.run(context -> { - assertThat(context).hasSingleBean( - PolarisCircuitBreakerAutoConfiguration.CircuitBreakerConfigModifier.class); + assertThat(context).hasSingleBean(PolarisCircuitBreakerAutoConfiguration.class); + assertThat(context).hasSingleBean(PolarisCircuitBreakerFeignClientAutoConfiguration.class); + assertThat(context).hasSingleBean(ReactivePolarisCircuitBreakerAutoConfiguration.class); }); } } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerFeignIntegrationTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerFeignIntegrationTest.java new file mode 100644 index 000000000..f3518f872 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerFeignIntegrationTest.java @@ -0,0 +1,234 @@ +/* + * 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; + + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration; +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.specification.api.v1.fault.tolerance.CircuitBreakerProto; +import com.tencent.polaris.test.common.TestUtils; +import com.tencent.polaris.test.mock.discovery.NamingServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import static com.tencent.polaris.test.common.TestUtils.SERVER_ADDRESS_ENV; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * @author sean yu + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, + classes = PolarisCircuitBreakerFeignIntegrationTest.TestConfig.class, + properties = { + "spring.cloud.gateway.enabled=false", + "feign.circuitbreaker.enabled=true", + "spring.cloud.polaris.namespace=default", + "spring.cloud.polaris.service=Test", + "spring.cloud.polaris.address=grpc://127.0.0.1:10081" + }) +@DirtiesContext +public class PolarisCircuitBreakerFeignIntegrationTest { + + private static final String TEST_SERVICE_NAME = "test-service-callee"; + private static NamingServer namingServer; + @Autowired + private EchoService echoService; + @Autowired + private FooService fooService; + @Autowired + private BarService barService; + @Autowired + private BazService bazService; + + @AfterAll + public static void afterAll() { + if (null != namingServer) { + namingServer.terminate(); + } + } + + @Test + public void contextLoads() throws Exception { + assertThat(echoService).isNotNull(); + assertThat(fooService).isNotNull(); + } + + @Test + public void testFeignClient() throws InvocationTargetException { + assertThat(echoService.echo("test")).isEqualTo("echo fallback"); + Utils.sleepUninterrupted(2000); + assertThatThrownBy(() -> { + echoService.echo(null); + }).isInstanceOf(Exception.class); + assertThatThrownBy(() -> { + fooService.echo("test"); + }).isInstanceOf(NoFallbackAvailableException.class); + Utils.sleepUninterrupted(2000); + assertThat(barService.bar()).isEqualTo("\"fallback from polaris server\""); + Utils.sleepUninterrupted(2000); + assertThat(bazService.baz()).isEqualTo("\"fallback from polaris server\""); + assertThat(fooService.toString()).isNotEqualTo(echoService.toString()); + assertThat(fooService.hashCode()).isNotEqualTo(echoService.hashCode()); + assertThat(echoService.equals(fooService)).isEqualTo(Boolean.FALSE); + } + + @FeignClient(value = TEST_SERVICE_NAME, contextId = "1", fallback = EchoServiceFallback.class) + public interface EchoService { + + @RequestMapping(path = "echo/{str}") + String echo(@RequestParam("str") String param) throws InvocationTargetException; + + } + + @FeignClient(value = TEST_SERVICE_NAME, contextId = "2", fallbackFactory = CustomFallbackFactory.class) + public interface FooService { + + @RequestMapping("echo/{str}") + String echo(@RequestParam("str") String param); + + } + + @FeignClient(value = TEST_SERVICE_NAME, contextId = "3") + public interface BarService { + + @RequestMapping(path = "bar") + String bar(); + + } + + public interface BazService { + + @RequestMapping(path = "baz") + String baz(); + + } + + @FeignClient(value = TEST_SERVICE_NAME, contextId = "4") + public interface BazClient extends BazService { + + } + + @Configuration + @EnableAutoConfiguration + @ImportAutoConfiguration({PolarisCircuitBreakerFeignClientAutoConfiguration.class}) + @EnableFeignClients + public static class TestConfig { + + @Bean + public EchoServiceFallback echoServiceFallback() { + return new EchoServiceFallback(); + } + + @Bean + public CustomFallbackFactory customFallbackFactory() { + return new CustomFallbackFactory(); + } + + @Bean + public CircuitBreakAPI circuitBreakAPI() throws InvalidProtocolBufferException { + try { + namingServer = NamingServer.startNamingServer(10081); + System.setProperty(SERVER_ADDRESS_ENV, String.format("127.0.0.1:%d", namingServer.getPort())); + } + catch (IOException e) { + + } + ServiceKey serviceKey = new ServiceKey("default", TEST_SERVICE_NAME); + + CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder(); + InputStream inputStream = PolarisCircuitBreakerMockServerTest.class.getClassLoader() + .getResourceAsStream("circuitBreakerRule.json"); + String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines() + .collect(Collectors.joining("")); + JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder); + CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build(); + CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder() + .addRules(circuitBreakerRule).build(); + namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker); + com.tencent.polaris.api.config.Configuration configuration = TestUtils.configWithEnvAddress(); + return CircuitBreakAPIFactory.createCircuitBreakAPIByConfig(configuration); + } + + } + + public static class EchoServiceFallback implements EchoService { + + @Override + public String echo(@RequestParam("str") String param) throws InvocationTargetException { + if (param == null) { + throw new InvocationTargetException(new Exception()); + } + return "echo fallback"; + } + + } + + public static class FooServiceFallback implements FooService { + + @Override + public String echo(@RequestParam("str") String param) { + throw new NoFallbackAvailableException("fallback", new RuntimeException()); + } + + } + + public static class CustomFallbackFactory + implements FallbackFactory<FooService> { + + private final FooService fooService = new FooServiceFallback(); + + @Override + public FooService create(Throwable throwable) { + return fooService; + } + + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerGatewayIntegrationTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerGatewayIntegrationTest.java new file mode 100644 index 000000000..af254508f --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerGatewayIntegrationTest.java @@ -0,0 +1,231 @@ +/* + * 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; + + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.tencent.cloud.polaris.circuitbreaker.gateway.PolarisCircuitBreakerFilterFactory; +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.specification.api.v1.fault.tolerance.CircuitBreakerProto; +import com.tencent.polaris.test.common.TestUtils; +import com.tencent.polaris.test.mock.discovery.NamingServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerFilterFactory; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static com.tencent.polaris.test.common.TestUtils.SERVER_ADDRESS_ENV; +import static org.assertj.core.api.Assertions.assertThat; + + +/** + * @author sean yu + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { + "spring.cloud.gateway.enabled=true", + "spring.cloud.polaris.namespace=default", + "spring.cloud.polaris.service=Test", + "spring.main.web-application-type=reactive", + "httpbin=http://localhost:${wiremock.server.port}", + "spring.cloud.polaris.address=grpc://127.0.0.1:10081" + }, + classes = PolarisCircuitBreakerGatewayIntegrationTest.TestApplication.class +) +@AutoConfigureWireMock(port = 0) +@ActiveProfiles("test-gateway") +@AutoConfigureWebTestClient(timeout = "1000000") +public class PolarisCircuitBreakerGatewayIntegrationTest { + + private static final String TEST_SERVICE_NAME = "test-service-callee"; + private static NamingServer namingServer; + @Autowired + private WebTestClient webClient; + @Autowired + private ApplicationContext applicationContext; + + @AfterAll + public static void afterAll() { + if (null != namingServer) { + namingServer.terminate(); + } + } + + @Test + public void fallback() throws Exception { + SpringCloudCircuitBreakerFilterFactory.Config config = new SpringCloudCircuitBreakerFilterFactory.Config(); + applicationContext.getBean(PolarisCircuitBreakerFilterFactory.class).apply(config).toString(); + + webClient + .get().uri("/err") + .header("Host", "www.circuitbreaker.com") + .exchange() + .expectStatus().isOk() + .expectBody() + .consumeWith( + response -> assertThat(response.getResponseBody()).isEqualTo("fallback".getBytes())); + + Utils.sleepUninterrupted(2000); + + webClient + .get().uri("/err-skip-fallback") + .header("Host", "www.circuitbreaker-skip-fallback.com") + .exchange() + .expectStatus(); + + Utils.sleepUninterrupted(2000); + + // this should be 200, but for some unknown reason, GitHub action run failed in windows, so we skip this check + webClient + .get().uri("/err-skip-fallback") + .header("Host", "www.circuitbreaker-skip-fallback.com") + .exchange() + .expectStatus(); + + Utils.sleepUninterrupted(2000); + + webClient + .get().uri("/err-no-fallback") + .header("Host", "www.circuitbreaker-no-fallback.com") + .exchange() + .expectStatus(); + + Utils.sleepUninterrupted(2000); + + webClient + .get().uri("/err-no-fallback") + .header("Host", "www.circuitbreaker-no-fallback.com") + .exchange() + .expectStatus(); + + Utils.sleepUninterrupted(2000); + + webClient + .get().uri("/err-no-fallback") + .header("Host", "www.circuitbreaker-no-fallback.com") + .exchange() + .expectStatus(); + } + + + @Configuration + @EnableAutoConfiguration + public static class TestApplication { + + @Bean + public CircuitBreakAPI circuitBreakAPI() throws InvalidProtocolBufferException { + try { + namingServer = NamingServer.startNamingServer(10081); + System.setProperty(SERVER_ADDRESS_ENV, String.format("127.0.0.1:%d", namingServer.getPort())); + } + catch (IOException e) { + + } + ServiceKey serviceKey = new ServiceKey("default", TEST_SERVICE_NAME); + + CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder(); + InputStream inputStream = PolarisCircuitBreakerMockServerTest.class.getClassLoader() + .getResourceAsStream("circuitBreakerRule.json"); + String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines() + .collect(Collectors.joining("")); + JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder); + CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build(); + CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder() + .addRules(circuitBreakerRule).build(); + namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker); + com.tencent.polaris.api.config.Configuration configuration = TestUtils.configWithEnvAddress(); + return CircuitBreakAPIFactory.createCircuitBreakAPIByConfig(configuration); + } + + @Bean + public RouteLocator myRoutes(RouteLocatorBuilder builder) { + Set<String> codeSets = new HashSet<>(); + codeSets.add("4**"); + codeSets.add("5**"); + return builder.routes() + .route(p -> p + .host("*.circuitbreaker.com") + .filters(f -> f + .circuitBreaker(config -> config + .setStatusCodes(codeSets) + .setFallbackUri("forward:/fallback") + .setName(TEST_SERVICE_NAME) + )) + .uri("http://httpbin.org:80")) + .route(p -> p + .host("*.circuitbreaker-skip-fallback.com") + .filters(f -> f + .circuitBreaker(config -> config + .setStatusCodes(Collections.singleton("5**")) + .setName(TEST_SERVICE_NAME) + )) + .uri("http://httpbin.org:80")) + .route(p -> p + .host("*.circuitbreaker-no-fallback.com") + .filters(f -> f + .circuitBreaker(config -> config + .setName(TEST_SERVICE_NAME) + )) + .uri("lb://" + TEST_SERVICE_NAME)) + .build(); + } + + @RestController + static class Controller { + @RequestMapping("/fallback") + public Mono<String> fallback() { + return Mono.just("fallback"); + } + } + + } + +} 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 new file mode 100644 index 000000000..abe66d7f7 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerIntegrationTest.java @@ -0,0 +1,309 @@ +/* + * 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; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.stream.Collectors; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration; +import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker; +import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerFallback; +import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerHttpResponse; +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.specification.api.v1.fault.tolerance.CircuitBreakerProto; +import com.tencent.polaris.test.common.TestUtils; +import com.tencent.polaris.test.mock.discovery.NamingServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.client.ExpectedCount; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriBuilderFactory; + +import static com.tencent.polaris.test.common.TestUtils.SERVER_ADDRESS_ENV; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; + +/** + * @author sean yu + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, + classes = PolarisCircuitBreakerIntegrationTest.TestConfig.class, + properties = { + "spring.cloud.gateway.enabled=false", + "feign.circuitbreaker.enabled=true", + "spring.cloud.polaris.namespace=default", + "spring.cloud.polaris.service=test", + "spring.cloud.polaris.address=grpc://127.0.0.1:10081" + }) +@DirtiesContext +public class PolarisCircuitBreakerIntegrationTest { + + private static final String TEST_SERVICE_NAME = "test-service-callee"; + + private static NamingServer namingServer; + @Autowired + @Qualifier("defaultRestTemplate") + private RestTemplate defaultRestTemplate; + @Autowired + @Qualifier("restTemplateFallbackFromPolaris") + private RestTemplate restTemplateFallbackFromPolaris; + @Autowired + @Qualifier("restTemplateFallbackFromCode") + private RestTemplate restTemplateFallbackFromCode; + @Autowired + @Qualifier("restTemplateFallbackFromCode2") + private RestTemplate restTemplateFallbackFromCode2; + @Autowired + @Qualifier("restTemplateFallbackFromCode3") + private RestTemplate restTemplateFallbackFromCode3; + @Autowired + @Qualifier("restTemplateFallbackFromCode4") + private RestTemplate restTemplateFallbackFromCode4; + @Autowired + private ApplicationContext applicationContext; + + @AfterAll + public static void afterAll() { + if (null != namingServer) { + namingServer.terminate(); + } + } + + @Test + public void testRestTemplate() throws URISyntaxException { + MockRestServiceServer mockServer = MockRestServiceServer.createServer(defaultRestTemplate); + mockServer + .expect(ExpectedCount.once(), requestTo(new URI("http://localhost:18001/example/service/b/info"))) + .andExpect(method(HttpMethod.GET)) + .andRespond(withStatus(HttpStatus.OK).body("OK")); + assertThat(defaultRestTemplate.getForObject("http://localhost:18001/example/service/b/info", String.class)).isEqualTo("OK"); + mockServer.verify(); + mockServer.reset(); + HttpHeaders headers = new HttpHeaders(); + mockServer + .expect(ExpectedCount.once(), requestTo(new URI("http://localhost:18001/example/service/b/info"))) + .andExpect(method(HttpMethod.GET)) + .andRespond(withStatus(HttpStatus.BAD_GATEWAY).headers(headers).body("BAD_GATEWAY")); + assertThat(defaultRestTemplate.getForObject("http://localhost:18001/example/service/b/info", String.class)).isEqualTo("fallback"); + mockServer.verify(); + mockServer.reset(); + assertThat(restTemplateFallbackFromCode.getForObject("/example/service/b/info", String.class)).isEqualTo("\"this is a fallback class\""); + Utils.sleepUninterrupted(2000); + assertThat(restTemplateFallbackFromCode2.getForObject("/example/service/b/info", String.class)).isEqualTo("\"this is a fallback class\""); + Utils.sleepUninterrupted(2000); + assertThat(restTemplateFallbackFromCode3.getForEntity("/example/service/b/info", String.class) + .getStatusCode()).isEqualTo(HttpStatus.OK); + Utils.sleepUninterrupted(2000); + assertThat(restTemplateFallbackFromCode4.getForObject("/example/service/b/info", String.class)).isEqualTo("fallback"); + Utils.sleepUninterrupted(2000); + assertThat(restTemplateFallbackFromPolaris.getForObject("/example/service/b/info", String.class)).isEqualTo("\"fallback from polaris server\""); + // just for code coverage + PolarisCircuitBreakerHttpResponse response = ((CustomPolarisCircuitBreakerFallback) applicationContext.getBean("customPolarisCircuitBreakerFallback")).fallback(); + assertThat(response.getStatusText()).isEqualTo("OK"); + assertThat(response.getFallbackInfo().getCode()).isEqualTo(200); + } + + @Configuration + @EnableAutoConfiguration + @ImportAutoConfiguration({PolarisCircuitBreakerFeignClientAutoConfiguration.class}) + @EnableFeignClients + public static class TestConfig { + + @Bean + @PolarisCircuitBreaker(fallback = "fallback") + public RestTemplate defaultRestTemplate() { + return new RestTemplate(); + } + + @Bean + @LoadBalanced + @PolarisCircuitBreaker + public RestTemplate restTemplateFallbackFromPolaris() { + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + return restTemplate; + } + + @Bean + @LoadBalanced + @PolarisCircuitBreaker(fallbackClass = CustomPolarisCircuitBreakerFallback.class) + public RestTemplate restTemplateFallbackFromCode() { + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + return restTemplate; + } + + @Bean + @LoadBalanced + @PolarisCircuitBreaker(fallbackClass = CustomPolarisCircuitBreakerFallback2.class) + public RestTemplate restTemplateFallbackFromCode2() { + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + return restTemplate; + } + + @Bean + @LoadBalanced + @PolarisCircuitBreaker(fallbackClass = CustomPolarisCircuitBreakerFallback3.class) + public RestTemplate restTemplateFallbackFromCode3() { + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + return restTemplate; + } + + @Bean + @LoadBalanced + @PolarisCircuitBreaker(fallback = "fallback") + public RestTemplate restTemplateFallbackFromCode4() { + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + return restTemplate; + } + + @Bean + public CustomPolarisCircuitBreakerFallback customPolarisCircuitBreakerFallback() { + return new CustomPolarisCircuitBreakerFallback(); + } + + @Bean + public CustomPolarisCircuitBreakerFallback2 customPolarisCircuitBreakerFallback2() { + return new CustomPolarisCircuitBreakerFallback2(); + } + + @Bean + public CustomPolarisCircuitBreakerFallback3 customPolarisCircuitBreakerFallback3() { + return new CustomPolarisCircuitBreakerFallback3(); + } + + @Bean + public CircuitBreakAPI circuitBreakAPI() throws InvalidProtocolBufferException { + try { + namingServer = NamingServer.startNamingServer(10081); + System.setProperty(SERVER_ADDRESS_ENV, String.format("127.0.0.1:%d", namingServer.getPort())); + } + catch (IOException e) { + + } + ServiceKey serviceKey = new ServiceKey("default", TEST_SERVICE_NAME); + + CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder(); + InputStream inputStream = PolarisCircuitBreakerMockServerTest.class.getClassLoader() + .getResourceAsStream("circuitBreakerRule.json"); + String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines() + .collect(Collectors.joining("")); + JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder); + CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build(); + CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder() + .addRules(circuitBreakerRule).build(); + namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker); + com.tencent.polaris.api.config.Configuration configuration = TestUtils.configWithEnvAddress(); + return CircuitBreakAPIFactory.createCircuitBreakAPIByConfig(configuration); + } + + @RestController + @RequestMapping("/example/service/b") + public class ServiceBController { + + /** + * Get service information. + * + * @return service information + */ + @GetMapping("/info") + public String info() { + return "hello world ! I'm a service B1"; + } + + } + + } + + public static class CustomPolarisCircuitBreakerFallback implements PolarisCircuitBreakerFallback { + @Override + public PolarisCircuitBreakerHttpResponse fallback() { + return new PolarisCircuitBreakerHttpResponse( + 200, + new HashMap<String, String>() {{ + put("xxx", "xxx"); + }}, + "\"this is a fallback class\""); + } + } + + public static class CustomPolarisCircuitBreakerFallback2 implements PolarisCircuitBreakerFallback { + @Override + public PolarisCircuitBreakerHttpResponse fallback() { + return new PolarisCircuitBreakerHttpResponse( + 200, + "\"this is a fallback class\"" + ); + } + } + + public static class CustomPolarisCircuitBreakerFallback3 implements PolarisCircuitBreakerFallback { + @Override + public PolarisCircuitBreakerHttpResponse fallback() { + return new PolarisCircuitBreakerHttpResponse( + 200 + ); + } + } + +} 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 new file mode 100644 index 000000000..5058a8a22 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerMockServerTest.java @@ -0,0 +1,152 @@ +/* + * 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; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +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; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; +import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker; + +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.SERVICE_CIRCUIT_BREAKER; +import static com.tencent.polaris.test.common.TestUtils.SERVER_ADDRESS_ENV; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for {@link PolarisCircuitBreaker} and {@link ReactivePolarisCircuitBreaker} using real mock server. + * + * @author sean yu + */ +@ExtendWith(MockitoExtension.class) +public class PolarisCircuitBreakerMockServerTest { + + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + private static NamingServer namingServer; + + @BeforeAll + public static void beforeAll() throws IOException { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties("spring.cloud.polaris.namespace")) + .thenReturn(NAMESPACE_TEST); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties("spring.cloud.polaris.service")) + .thenReturn(SERVICE_CIRCUIT_BREAKER); + + try { + namingServer = NamingServer.startNamingServer(-1); + System.setProperty(SERVER_ADDRESS_ENV, String.format("127.0.0.1:%d", namingServer.getPort())); + } + catch (IOException e) { + + } + ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, SERVICE_CIRCUIT_BREAKER); + + CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder(); + InputStream inputStream = PolarisCircuitBreakerMockServerTest.class.getClassLoader().getResourceAsStream("circuitBreakerRule.json"); + String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("")); + JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder); + CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build(); + CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder().addRules(circuitBreakerRule).build(); + namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker); + } + + @AfterAll + public static void afterAll() { + if (null != namingServer) { + namingServer.terminate(); + } + } + + @Test + public void testCircuitBreaker() { + Configuration configuration = TestUtils.configWithEnvAddress(); + CircuitBreakAPI circuitBreakAPI = CircuitBreakAPIFactory.createCircuitBreakAPIByConfig(configuration); + + ConsumerAPI consumerAPI = DiscoveryAPIFactory.createConsumerAPIByConfig(configuration); + PolarisCircuitBreakerFactory polarisCircuitBreakerFactory = new PolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI); + CircuitBreaker cb = polarisCircuitBreakerFactory.create(SERVICE_CIRCUIT_BREAKER); + + // trigger fallback for 5 times + List<String> resList = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + int finalI = i; + String res = cb.run(() -> { + if (finalI % 2 == 1) { + throw new IllegalArgumentException("invoke failed"); + } + else { + return "invoke success"; + } + }, t -> "fallback"); + resList.add(res); + Utils.sleepUninterrupted(2000); + } + assertThat(resList).isEqualTo(Arrays.asList("invoke success", "fallback", "fallback", "fallback", "fallback")); + + // always fallback + 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"))) + .block()).isEqualTo("fallback"); + + assertThat(Mono.error(new RuntimeException("boom")).transform(it -> rcb.run(it, t -> Mono.just("fallback"))) + .block()).isEqualTo("fallback"); + + assertThat(Flux.just("foobar", "hello world").transform(it -> rcb.run(it, t -> Flux.just("fallback", "fallback"))) + .collectList().block()) + .isEqualTo(Arrays.asList("fallback", "fallback")); + + assertThat(Flux.error(new RuntimeException("boom")).transform(it -> rcb.run(it, t -> Flux.just("fallback"))) + .collectList().block()) + .isEqualTo(Collections.singletonList("fallback")); + + + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerTest.java new file mode 100644 index 000000000..494ae7798 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerTest.java @@ -0,0 +1,98 @@ +/* + * 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; + + +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder; +import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration; +import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration; +import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; +import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; + +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.SERVICE_CIRCUIT_BREAKER; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for {@link PolarisCircuitBreaker}. + * + * @author sean yu + */ +@ExtendWith(MockitoExtension.class) +public class PolarisCircuitBreakerTest { + + private static final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + PolarisContextAutoConfiguration.class, + RpcEnhancementAutoConfiguration.class, + LoadBalancerAutoConfiguration.class, + PolarisCircuitBreakerFeignClientAutoConfiguration.class, + PolarisCircuitBreakerAutoConfiguration.class)) + .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true"); + + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + + @BeforeAll + public static void beforeClass() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties("spring.cloud.polaris.namespace")) + .thenReturn(NAMESPACE_TEST); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties("spring.cloud.polaris.service")) + .thenReturn(SERVICE_CIRCUIT_BREAKER); + } + + @AfterAll + public static void afterAll() { + mockedApplicationContextAwareUtils.close(); + } + + @Test + public void run() { + contextRunner.run(context -> { + + PolarisCircuitBreakerFactory polarisCircuitBreakerFactory = context.getBean(PolarisCircuitBreakerFactory.class); + CircuitBreaker cb = polarisCircuitBreakerFactory.create(SERVICE_CIRCUIT_BREAKER); + + PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration configuration = + polarisCircuitBreakerFactory.configBuilder(SERVICE_CIRCUIT_BREAKER).build(); + + polarisCircuitBreakerFactory.configureDefault(id -> configuration); + + assertThat(cb.run(() -> "foobar")).isEqualTo("foobar"); + + assertThat((String) cb.run(() -> { + throw new RuntimeException("boom"); + }, t -> "fallback")).isEqualTo("fallback"); + + }); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreakerTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreakerTest.java new file mode 100644 index 000000000..f06502c87 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/ReactivePolarisCircuitBreakerTest.java @@ -0,0 +1,103 @@ +/* + * 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; + +import java.util.Arrays; +import java.util.Collections; + +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder; +import com.tencent.cloud.polaris.circuitbreaker.config.ReactivePolarisCircuitBreakerAutoConfiguration; +import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker; +import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; + +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.SERVICE_CIRCUIT_BREAKER; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for {@link ReactivePolarisCircuitBreaker}. + * + * @author sean yu + */ +@ExtendWith(MockitoExtension.class) +public class ReactivePolarisCircuitBreakerTest { + + private final ApplicationContextRunner reactiveContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + PolarisContextAutoConfiguration.class, + RpcEnhancementAutoConfiguration.class, + LoadBalancerAutoConfiguration.class, + ReactivePolarisCircuitBreakerAutoConfiguration.class)) + .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true"); + + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + + @BeforeAll + public static void beforeClass() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties("spring.cloud.polaris.namespace")) + .thenReturn(NAMESPACE_TEST); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties("spring.cloud.polaris.service")) + .thenReturn(SERVICE_CIRCUIT_BREAKER); + } + + @AfterAll + public static void afterAll() { + mockedApplicationContextAwareUtils.close(); + } + + @Test + public void run() { + this.reactiveContextRunner.run(context -> { + ReactivePolarisCircuitBreakerFactory polarisCircuitBreakerFactory = context.getBean(ReactivePolarisCircuitBreakerFactory.class); + ReactiveCircuitBreaker cb = polarisCircuitBreakerFactory.create(SERVICE_CIRCUIT_BREAKER); + + PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration configuration = + polarisCircuitBreakerFactory.configBuilder(SERVICE_CIRCUIT_BREAKER).build(); + + polarisCircuitBreakerFactory.configureDefault(id -> configuration); + + assertThat(Mono.just("foobar").transform(cb::run).block()).isEqualTo("foobar"); + + assertThat(Mono.error(new RuntimeException("boom")).transform(it -> cb.run(it, t -> Mono.just("fallback"))) + .block()).isEqualTo("fallback"); + + assertThat(Flux.just("foobar", "hello world").transform(cb::run).collectList().block()) + .isEqualTo(Arrays.asList("foobar", "hello world")); + + assertThat(Flux.error(new RuntimeException("boom")).transform(it -> cb.run(it, t -> Flux.just("fallback"))) + .collectList().block()).isEqualTo(Collections.singletonList("fallback")); + }); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/reporter/ExceptionCircuitBreakerReporterTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/reporter/ExceptionCircuitBreakerReporterTest.java new file mode 100644 index 000000000..53e38c9d2 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/reporter/ExceptionCircuitBreakerReporterTest.java @@ -0,0 +1,145 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.circuitbreaker.reporter; + +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.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; +import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; +import com.tencent.polaris.client.api.SDKContext; +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; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; + +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.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * ExceptionCircuitBreakerReporterTest. + * + * @author sean yu + */ +@ExtendWith(MockitoExtension.class) +public class ExceptionCircuitBreakerReporterTest { + + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + @Mock + private RpcEnhancementReporterProperties reporterProperties; + @Mock + private SDKContext sdkContext; + @InjectMocks + private ExceptionCircuitBreakerReporter exceptionCircuitBreakerReporter; + @Mock + private CircuitBreakAPI circuitBreakAPI; + + @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 testGetName() { + assertThat(exceptionCircuitBreakerReporter.getName()).isEqualTo(ExceptionCircuitBreakerReporter.class.getName()); + } + + @Test + public void testType() { + assertThat(exceptionCircuitBreakerReporter.getType()).isEqualTo(EnhancedPluginType.EXCEPTION); + } + + @Test + public void testRun() throws Throwable { + EnhancedPluginContext context = mock(EnhancedPluginContext.class); + // test not report + exceptionCircuitBreakerReporter.run(context); + verify(context, times(0)).getRequest(); + + doReturn(true).when(reporterProperties).isEnabled(); + + EnhancedPluginContext pluginContext = new EnhancedPluginContext(); + EnhancedRequestContext request = EnhancedRequestContext.builder() + .httpMethod(HttpMethod.GET) + .url(URI.create("http://0.0.0.0/")) + .httpHeaders(new HttpHeaders()) + .build(); + EnhancedResponseContext response = EnhancedResponseContext.builder() + .httpStatus(200) + .build(); + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); + serviceInstance.setServiceId(SERVICE_PROVIDER); + + pluginContext.setRequest(request); + pluginContext.setResponse(response); + pluginContext.setServiceInstance(serviceInstance); + pluginContext.setThrowable(new RuntimeException()); + + exceptionCircuitBreakerReporter.run(pluginContext); + exceptionCircuitBreakerReporter.getOrder(); + exceptionCircuitBreakerReporter.getName(); + exceptionCircuitBreakerReporter.getType(); + } + + @Test + public void testHandlerThrowable() { + // mock request + EnhancedRequestContext request = mock(EnhancedRequestContext.class); + // mock response + EnhancedResponseContext response = mock(EnhancedResponseContext.class); + + EnhancedPluginContext context = new EnhancedPluginContext(); + context.setRequest(request); + context.setResponse(response); + exceptionCircuitBreakerReporter.handlerThrowable(context, new RuntimeException("Mock exception.")); + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/reporter/SuccessCircuitBreakerReporterTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/reporter/SuccessCircuitBreakerReporterTest.java new file mode 100644 index 000000000..4dcd3d19e --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/reporter/SuccessCircuitBreakerReporterTest.java @@ -0,0 +1,143 @@ +/* + * 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.reporter; + +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.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; +import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; +import com.tencent.polaris.client.api.SDKContext; +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; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.http.HttpMethod; + +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.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * SuccessCircuitBreakerReporterTest. + * + * @author sean yu + */ +@ExtendWith(MockitoExtension.class) +public class SuccessCircuitBreakerReporterTest { + + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + @Mock + private SDKContext sdkContext; + @Mock + private RpcEnhancementReporterProperties reporterProperties; + @InjectMocks + private SuccessCircuitBreakerReporter successCircuitBreakerReporter; + @Mock + private CircuitBreakAPI circuitBreakAPI; + + @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 testGetName() { + assertThat(successCircuitBreakerReporter.getName()).isEqualTo(SuccessCircuitBreakerReporter.class.getName()); + } + + @Test + public void testType() { + assertThat(successCircuitBreakerReporter.getType()).isEqualTo(EnhancedPluginType.POST); + } + + @Test + public void testRun() throws Throwable { + + EnhancedPluginContext context = mock(EnhancedPluginContext.class); + // test not report + successCircuitBreakerReporter.run(context); + verify(context, times(0)).getRequest(); + + doReturn(true).when(reporterProperties).isEnabled(); + + EnhancedPluginContext pluginContext = new EnhancedPluginContext(); + EnhancedRequestContext request = EnhancedRequestContext.builder() + .httpMethod(HttpMethod.GET) + .url(URI.create("http://0.0.0.0/")) + .build(); + EnhancedResponseContext response = EnhancedResponseContext.builder() + .httpStatus(200) + .build(); + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); + serviceInstance.setServiceId(SERVICE_PROVIDER); + + pluginContext.setRequest(request); + pluginContext.setResponse(response); + pluginContext.setServiceInstance(serviceInstance); + + successCircuitBreakerReporter.run(pluginContext); + successCircuitBreakerReporter.getOrder(); + successCircuitBreakerReporter.getName(); + successCircuitBreakerReporter.getType(); + } + + @Test + public void testHandlerThrowable() { + // mock request + EnhancedRequestContext request = mock(EnhancedRequestContext.class); + // mock response + EnhancedResponseContext response = mock(EnhancedResponseContext.class); + + EnhancedPluginContext context = new EnhancedPluginContext(); + context.setRequest(request); + context.setResponse(response); + successCircuitBreakerReporter.handlerThrowable(context, new RuntimeException("Mock exception.")); + } +} 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<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<>(), ""))); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/application-test-gateway.yml b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/application-test-gateway.yml new file mode 100644 index 000000000..6094b46e5 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/application-test-gateway.yml @@ -0,0 +1,36 @@ +spring: + application: + name: GatewayScgService + cloud: + tencent: + plugin: + scg: + staining: + enabled: true + rule-staining: + enabled: true + router: + feature-env: + enabled: true + polaris: + address: grpc://127.0.0.1:8091 + namespace: default + enabled: true + gateway: + routes: + - id: cb-test + uri: http://localhost:${server.port}/hello/1 + predicates: + - Path=/cb-test/** + filters: + - name: CircuitBreaker + args: + statusCodes: 5**,4**,3**,2**,1**,500,400 + fallbackUri: forward:/polaris-fallback +logging: + level: + root: info + com.tencent.polaris.discovery.client.flow.RegisterFlow: off + com.tencent.polaris.plugins.registry.memory.CacheObject: off + com.tencent.cloud.polaris.circuitbreaker: debug + diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/circuitBreakerRule-method.json b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/circuitBreakerRule-method.json new file mode 100644 index 000000000..749e43479 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/circuitBreakerRule-method.json @@ -0,0 +1,59 @@ +{ + "@type": "type.googleapis.com/v1.CircuitBreakerRule", + "id": "5f1601f01823474d9be39c0bbb26ab87", + "name": "test", + "namespace": "TestCircuitBreakerRule", + "enable": true, + "revision": "10b120c08706429f8fdc3fb44a53224b", + "ctime": "1754-08-31 06:49:24", + "mtime": "2023-02-21 17:35:31", + "etime": "", + "description": "", + "level": "METHOD", + "ruleMatcher": { + "source": { + "service": "*", + "namespace": "*" + }, + "destination": { + "service": "*", + "namespace": "*", + "method": {"type": "REGEX", "value": "*"} + } + }, + "errorConditions": [ + { + "inputType": "RET_CODE", + "condition": { + "type": "NOT_EQUALS", + "value": "200", + "valueType": "TEXT" + } + } + ], + "triggerCondition": [ + { + "triggerType": "CONSECUTIVE_ERROR", + "errorCount": 1, + "errorPercent": 1, + "interval": 5, + "minimumRequest": 5 + } + ], + "maxEjectionPercent": 0, + "recoverCondition": { + "sleepWindow": 60, + "consecutiveSuccess": 3 + }, + "faultDetectConfig": { + "enable": true + }, + "fallbackConfig": { + "enable": true, + "response": { + "code": 200, + "headers": [{"key": "xxx", "value": "xxx"}], + "body": "\"fallback from polaris server\"" + } + } +} \ No newline at end of file diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/circuitBreakerRule.json b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/circuitBreakerRule.json new file mode 100644 index 000000000..7adef7fba --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/circuitBreakerRule.json @@ -0,0 +1,59 @@ +{ + "@type": "type.googleapis.com/v1.CircuitBreakerRule", + "id": "5f1601f01823474d9be39c0bbb26ab87", + "name": "test", + "namespace": "TestCircuitBreakerRule", + "enable": true, + "revision": "10b120c08706429f8fdc3fb44a53224b", + "ctime": "1754-08-31 06:49:24", + "mtime": "2023-02-21 17:35:31", + "etime": "", + "description": "", + "level": "SERVICE", + "ruleMatcher": { + "source": { + "service": "*", + "namespace": "*" + }, + "destination": { + "service": "*", + "namespace": "*", + "method": null + } + }, + "errorConditions": [ + { + "inputType": "RET_CODE", + "condition": { + "type": "NOT_EQUALS", + "value": "200", + "valueType": "TEXT" + } + } + ], + "triggerCondition": [ + { + "triggerType": "CONSECUTIVE_ERROR", + "errorCount": 1, + "errorPercent": 1, + "interval": 5, + "minimumRequest": 5 + } + ], + "maxEjectionPercent": 0, + "recoverCondition": { + "sleepWindow": 60, + "consecutiveSuccess": 3 + }, + "faultDetectConfig": { + "enable": true + }, + "fallbackConfig": { + "enable": true, + "response": { + "code": 200, + "headers": [{"key": "xxx", "value": "xxx"}], + "body": "\"fallback from polaris server\"" + } + } +} \ No newline at end of file diff --git a/spring-cloud-starter-tencent-polaris-discovery/pom.xml b/spring-cloud-starter-tencent-polaris-discovery/pom.xml index c16e5ffcb..7b881c93e 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/pom.xml +++ b/spring-cloud-starter-tencent-polaris-discovery/pom.xml @@ -32,6 +32,18 @@ <scope>test</scope> </dependency> + <dependency> + <groupId>com.tencent.polaris</groupId> + <artifactId>connector-consul</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>com.tencent.polaris</groupId> + <artifactId>connector-nacos</artifactId> + <scope>test</scope> + </dependency> + <dependency> <groupId>com.tencent.polaris</groupId> <artifactId>polaris-test-mock-discovery</artifactId> diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/extend/consul/ConsulConfigModifier.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/extend/consul/ConsulConfigModifier.java index 70a4f703f..e9bed3e61 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/extend/consul/ConsulConfigModifier.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/extend/consul/ConsulConfigModifier.java @@ -31,6 +31,8 @@ import com.tencent.polaris.factory.config.global.ServerConnectorConfigImpl; import com.tencent.polaris.factory.config.provider.RegisterConfigImpl; import com.tencent.polaris.plugins.connector.common.constant.ConsulConstant; import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.util.CollectionUtils; @@ -38,6 +40,9 @@ import org.springframework.util.CollectionUtils; * @author lingxiao.wlx */ public class ConsulConfigModifier implements PolarisConfigModifier { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConsulConfigModifier.class); + private static final String ID = "consul"; private final ConsulContextProperties consulContextProperties; @@ -49,6 +54,23 @@ public class ConsulConfigModifier implements PolarisConfigModifier { @Override public void modify(ConfigurationImpl configuration) { if (consulContextProperties != null && consulContextProperties.isEnabled()) { + // Check if Consul client Available + boolean consulAvailable = false; + try { + consulAvailable = null != Class.forName("com.ecwid.consul.v1.ConsulClient"); + } + catch (Throwable ignored) { + + } + if (!consulAvailable) { + LOGGER.error("Please import \"connector-consul\" dependency when enabling consul service registration and discovery.\n" + + "Add dependency configuration below to pom.xml:\n" + + "<dependency>\n" + + "\t<groupId>com.tencent.polaris</groupId>\n" + + "\t<artifactId>connector-consul</artifactId>\n" + + "</dependency>"); + throw new RuntimeException("Dependency \"connector-consul\" not found."); + } if (CollectionUtils.isEmpty(configuration.getGlobal().getServerConnectors())) { configuration.getGlobal().setServerConnectors(new ArrayList<>()); } diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/extend/nacos/NacosConfigModifier.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/extend/nacos/NacosConfigModifier.java index ca80e5038..4f6838177 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/extend/nacos/NacosConfigModifier.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/extend/nacos/NacosConfigModifier.java @@ -30,6 +30,8 @@ import com.tencent.polaris.factory.config.consumer.DiscoveryConfigImpl; import com.tencent.polaris.factory.config.global.ServerConnectorConfigImpl; import com.tencent.polaris.factory.config.provider.RegisterConfigImpl; import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.util.CollectionUtils; @@ -40,7 +42,6 @@ import org.springframework.util.CollectionUtils; */ public class NacosConfigModifier implements PolarisConfigModifier { - private static final String ID = "nacos"; /** * nacos username. */ @@ -53,7 +54,8 @@ public class NacosConfigModifier implements PolarisConfigModifier { * nacos contextPath. */ public static final String CONTEXT_PATH = "contextPath"; - + private static final Logger LOGGER = LoggerFactory.getLogger(NacosConfigModifier.class); + private static final String ID = "nacos"; private final NacosContextProperties nacosContextProperties; public NacosConfigModifier(NacosContextProperties nacosContextProperties) { @@ -65,6 +67,23 @@ public class NacosConfigModifier implements PolarisConfigModifier { if (Objects.isNull(nacosContextProperties) || !nacosContextProperties.isEnabled()) { return; } + // Check if Nacos Available + boolean nacosAvailable = false; + try { + nacosAvailable = null != Class.forName("com.alibaba.nacos.api.naming.NamingService"); + } + catch (Throwable ignored) { + + } + if (!nacosAvailable) { + LOGGER.error("Please import \"connector-nacos\" dependency when enabling nacos service registration and discovery.\n" + + "Add dependency configuration below to pom.xml:\n" + + "<dependency>\n" + + "\t<groupId>com.tencent.polaris</groupId>\n" + + "\t<artifactId>connector-nacos</artifactId>\n" + + "</dependency>"); + throw new RuntimeException("Dependency \"connector-nacos\" not found."); + } if (CollectionUtils.isEmpty(configuration.getGlobal().getServerConnectors())) { configuration.getGlobal().setServerConnectors(new ArrayList<>()); } diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistry.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistry.java index 269c6931b..e320ed531 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistry.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistry.java @@ -41,6 +41,7 @@ import com.tencent.polaris.api.rpc.InstanceRegisterRequest; import com.tencent.polaris.api.rpc.InstanceRegisterResponse; import com.tencent.polaris.api.rpc.InstancesResponse; import com.tencent.polaris.client.util.NamedThreadFactory; +import com.tencent.polaris.factory.config.provider.ServiceConfigImpl; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -144,6 +145,11 @@ public class PolarisServiceRegistry implements ServiceRegistry<PolarisRegistrati LOGGER.warn("Stat server started error, ", e); } } + + ServiceConfigImpl serviceConfig = (ServiceConfigImpl) polarisDiscoveryHandler.getSdkContext().getConfig() + .getProvider().getService(); + serviceConfig.setNamespace(polarisDiscoveryProperties.getNamespace()); + serviceConfig.setName(serviceId); } catch (Exception e) { LOGGER.error("polaris registry, {} register failed...{},", registration.getServiceId(), registration, e); @@ -197,7 +203,7 @@ public class PolarisServiceRegistry implements ServiceRegistry<PolarisRegistrati String serviceName = registration.getServiceId(); InstancesResponse instancesResponse = polarisDiscoveryHandler.getInstances(serviceName); Instance[] instances = instancesResponse.getInstances(); - if (null == instances || instances.length == 0) { + if (null == instances) { return null; } for (Instance instance : instances) { diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListConfigurationTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListConfigurationTest.java index 42448d2b7..715ab2f39 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListConfigurationTest.java @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; +import static com.tencent.polaris.test.common.Consts.PORT; import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; import static org.assertj.core.api.Assertions.assertThat; @@ -44,6 +45,9 @@ public class PolarisRibbonServerListConfigurationTest { this.applicationContextRunner .withConfiguration(AutoConfigurations.of( TestApplication.class, PolarisRibbonServerListConfiguration.class)) + .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) + .withPropertyValues("server.port=" + PORT) + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081") .run(context -> { assertThat(context).hasSingleBean(PolarisRibbonServerListConfiguration.class); assertThat(context).hasSingleBean(ServerList.class); diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/util/OkHttpUtilTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/util/OkHttpUtilTest.java index 7590b8b1e..b718e88e9 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/util/OkHttpUtilTest.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/util/OkHttpUtilTest.java @@ -37,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Haotian Zhang */ @ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = OkHttpUtilTest.TestApplication.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = OkHttpUtilTest.TestApplication.class, properties = {"spring.application.name=test", "spring.cloud.polaris.discovery.register=false"}) public class OkHttpUtilTest { @LocalServerPort diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/pom.xml b/spring-cloud-starter-tencent-polaris-ratelimit/pom.xml index a7ee8e340..0b08cd790 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/pom.xml +++ b/spring-cloud-starter-tencent-polaris-ratelimit/pom.xml @@ -15,10 +15,10 @@ <dependencies> <!-- Spring Cloud Tencent dependencies start --> - <dependency> - <groupId>com.tencent.cloud</groupId> - <artifactId>spring-cloud-tencent-polaris-context</artifactId> - </dependency> + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-tencent-rpc-enhancement</artifactId> + </dependency> <!-- Spring Cloud Tencent dependencies end --> <!-- Polaris dependencies start --> diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitRuleLabelResolver.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitRuleLabelResolver.java index c0d3de241..f51b6f0b8 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitRuleLabelResolver.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitRuleLabelResolver.java @@ -34,8 +34,9 @@ import org.springframework.util.CollectionUtils; /** * resolve labels from rate limit rule. * - * @author lepdou 2022-05-13 + *@author lepdou 2022-05-13 */ +@Deprecated public class RateLimitRuleLabelResolver { private final ServiceRuleManager serviceRuleManager; diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitAutoConfiguration.java index b8b79b7a9..1a20a298f 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitAutoConfiguration.java @@ -20,10 +20,10 @@ package com.tencent.cloud.polaris.ratelimit.config; import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; -import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; -import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter; +import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentReactiveResolver; +import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServletResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; @@ -31,6 +31,7 @@ import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.factory.LimitAPIFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; @@ -39,6 +40,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.lang.Nullable; +import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.FILTER_ORDER; import static com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter.QUOTA_FILTER_BEAN_NAME; import static javax.servlet.DispatcherType.ASYNC; import static javax.servlet.DispatcherType.ERROR; @@ -62,11 +64,6 @@ public class PolarisRateLimitAutoConfiguration { return LimitAPIFactory.createLimitAPIByContext(polarisContext); } - @Bean - public RateLimitRuleLabelResolver rateLimitRuleLabelService(ServiceRuleManager serviceRuleManager) { - return new RateLimitRuleLabelResolver(serviceRuleManager); - } - /** * Create when web application type is SERVLET. */ @@ -74,15 +71,19 @@ public class PolarisRateLimitAutoConfiguration { @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) protected static class QuotaCheckFilterConfig { + @Bean + public RateLimitRuleArgumentServletResolver rateLimitRuleArgumentResolver(ServiceRuleManager serviceRuleManager, + @Autowired(required = false) PolarisRateLimiterLabelServletResolver labelResolver) { + return new RateLimitRuleArgumentServletResolver(serviceRuleManager, labelResolver); + } + @Bean @ConditionalOnMissingBean public QuotaCheckServletFilter quotaCheckFilter(LimitAPI limitAPI, - @Nullable PolarisRateLimiterLabelServletResolver labelResolver, PolarisRateLimitProperties polarisRateLimitProperties, - RateLimitRuleLabelResolver rateLimitRuleLabelResolver, - @Nullable PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) { - return new QuotaCheckServletFilter(limitAPI, labelResolver, - polarisRateLimitProperties, rateLimitRuleLabelResolver, polarisRateLimiterLimitedFallback); + RateLimitRuleArgumentServletResolver rateLimitRuleArgumentResolver, + @Autowired(required = false) PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) { + return new QuotaCheckServletFilter(limitAPI, polarisRateLimitProperties, rateLimitRuleArgumentResolver, polarisRateLimiterLimitedFallback); } @Bean @@ -92,9 +93,10 @@ public class PolarisRateLimitAutoConfiguration { quotaCheckServletFilter); registrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, REQUEST); registrationBean.setName(QUOTA_FILTER_BEAN_NAME); - registrationBean.setOrder(RateLimitConstant.FILTER_ORDER); + registrationBean.setOrder(FILTER_ORDER); return registrationBean; } + } /** @@ -104,14 +106,18 @@ public class PolarisRateLimitAutoConfiguration { @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) protected static class MetadataReactiveFilterConfig { + @Bean + public RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentResolver(ServiceRuleManager serviceRuleManager, + @Autowired(required = false) PolarisRateLimiterLabelReactiveResolver labelResolver) { + return new RateLimitRuleArgumentReactiveResolver(serviceRuleManager, labelResolver); + } + @Bean public QuotaCheckReactiveFilter quotaCheckReactiveFilter(LimitAPI limitAPI, - @Nullable PolarisRateLimiterLabelReactiveResolver labelResolver, PolarisRateLimitProperties polarisRateLimitProperties, - RateLimitRuleLabelResolver rateLimitRuleLabelResolver, + RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentResolver, @Nullable PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) { - return new QuotaCheckReactiveFilter(limitAPI, labelResolver, - polarisRateLimitProperties, rateLimitRuleLabelResolver, polarisRateLimiterLimitedFallback); + return new QuotaCheckReactiveFilter(limitAPI, polarisRateLimitProperties, rateLimitRuleArgumentResolver, polarisRateLimiterLimitedFallback); } } } 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 776e776a5..b3129eed0 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 @@ -20,27 +20,22 @@ package com.tencent.cloud.polaris.ratelimit.filter; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; import java.util.Set; import javax.annotation.PostConstruct; -import com.google.common.collect.Maps; import com.tencent.cloud.common.metadata.MetadataContext; -import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; -import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; -import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver; +import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentReactiveResolver; 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.ratelimit.api.core.LimitAPI; +import com.tencent.polaris.ratelimit.api.rpc.Argument; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode; -import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; @@ -54,8 +49,6 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; -import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LABEL_METHOD; - /** * Reactive filter to check quota. * @@ -67,11 +60,9 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { private final LimitAPI limitAPI; - private final PolarisRateLimiterLabelReactiveResolver labelResolver; - private final PolarisRateLimitProperties polarisRateLimitProperties; - private final RateLimitRuleLabelResolver rateLimitRuleLabelResolver; + private final RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentResolver; private final PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback; @@ -79,14 +70,12 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { private String rejectTips; public QuotaCheckReactiveFilter(LimitAPI limitAPI, - PolarisRateLimiterLabelReactiveResolver labelResolver, - PolarisRateLimitProperties polarisRateLimitProperties, - RateLimitRuleLabelResolver rateLimitRuleLabelResolver, - @Nullable PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) { + PolarisRateLimitProperties polarisRateLimitProperties, + RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentResolver, + @Nullable PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) { this.limitAPI = limitAPI; - this.labelResolver = labelResolver; this.polarisRateLimitProperties = polarisRateLimitProperties; - this.rateLimitRuleLabelResolver = rateLimitRuleLabelResolver; + this.rateLimitRuleArgumentResolver = rateLimitRuleArgumentResolver; this.polarisRateLimiterLimitedFallback = polarisRateLimiterLimitedFallback; } @@ -105,12 +94,12 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { String localNamespace = MetadataContext.LOCAL_NAMESPACE; String localService = MetadataContext.LOCAL_SERVICE; - Map<String, String> labels = getRequestLabels(exchange, localNamespace, localService); + Set<Argument> arguments = rateLimitRuleArgumentResolver.getArguments(exchange, localNamespace, localService); long waitMs = -1; try { String path = exchange.getRequest().getURI().getPath(); - QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, - localNamespace, localService, 1, labels, path); + QuotaResponse quotaResponse = QuotaCheckUtils.getQuota( + limitAPI, localNamespace, localService, 1, arguments, path); if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { ServerHttpResponse response = exchange.getResponse(); @@ -119,7 +108,8 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { response.setRawStatusCode(polarisRateLimiterLimitedFallback.rejectHttpCode()); response.getHeaders().setContentType(polarisRateLimiterLimitedFallback.mediaType()); dataBuffer = response.bufferFactory().allocateBuffer() - .write(polarisRateLimiterLimitedFallback.rejectTips().getBytes(polarisRateLimiterLimitedFallback.charset())); + .write(polarisRateLimiterLimitedFallback.rejectTips() + .getBytes(polarisRateLimiterLimitedFallback.charset())); } else { response.setRawStatusCode(polarisRateLimitProperties.getRejectHttpCode()); @@ -149,40 +139,4 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { } } - private Map<String, String> getRequestLabels(ServerWebExchange exchange, String localNamespace, String localService) { - Map<String, String> labels = new HashMap<>(); - - // add build in labels - String path = exchange.getRequest().getURI().getPath(); - if (StringUtils.isNotBlank(path)) { - labels.put(LABEL_METHOD, path); - } - - // add rule expression labels - Map<String, String> expressionLabels = getRuleExpressionLabels(exchange, localNamespace, localService); - labels.putAll(expressionLabels); - - // add custom labels - Map<String, String> customResolvedLabels = getCustomResolvedLabels(exchange); - labels.putAll(customResolvedLabels); - - return labels; - } - - private Map<String, String> getCustomResolvedLabels(ServerWebExchange exchange) { - if (labelResolver != null) { - try { - return labelResolver.resolve(exchange); - } - catch (Throwable e) { - LOGGER.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e); - } - } - return Maps.newHashMap(); - } - - private Map<String, String> getRuleExpressionLabels(ServerWebExchange exchange, String namespace, String service) { - Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service); - return SpringWebExpressionLabelUtils.resolve(exchange, expressionLabels); - } } 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 434046434..a054d5be3 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 @@ -19,9 +19,6 @@ package com.tencent.cloud.polaris.ratelimit.filter; import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; import java.util.Set; @@ -31,19 +28,19 @@ 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.common.util.expresstion.ServletExpressionLabelUtils; -import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; -import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver; +import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServletResolver; 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; import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode; -import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,8 +50,6 @@ import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.web.filter.OncePerRequestFilter; -import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LABEL_METHOD; - /** * Servlet filter to check quota. * @@ -70,25 +65,21 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter { private static final Logger LOG = LoggerFactory.getLogger(QuotaCheckServletFilter.class); private final LimitAPI limitAPI; - private final PolarisRateLimiterLabelServletResolver labelResolver; - private final PolarisRateLimitProperties polarisRateLimitProperties; - private final RateLimitRuleLabelResolver rateLimitRuleLabelResolver; + private final RateLimitRuleArgumentServletResolver rateLimitRuleArgumentResolver; private final PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback; private String rejectTips; public QuotaCheckServletFilter(LimitAPI limitAPI, - PolarisRateLimiterLabelServletResolver labelResolver, PolarisRateLimitProperties polarisRateLimitProperties, - RateLimitRuleLabelResolver rateLimitRuleLabelResolver, + RateLimitRuleArgumentServletResolver rateLimitRuleArgumentResolver, @Nullable PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback) { this.limitAPI = limitAPI; - this.labelResolver = labelResolver; this.polarisRateLimitProperties = polarisRateLimitProperties; - this.rateLimitRuleLabelResolver = rateLimitRuleLabelResolver; + this.rateLimitRuleArgumentResolver = rateLimitRuleArgumentResolver; this.polarisRateLimiterLimitedFallback = polarisRateLimiterLimitedFallback; } @@ -104,11 +95,11 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter { String localNamespace = MetadataContext.LOCAL_NAMESPACE; String localService = MetadataContext.LOCAL_SERVICE; - Map<String, String> labels = getRequestLabels(request, localNamespace, localService); + Set<Argument> arguments = rateLimitRuleArgumentResolver.getArguments(request, localNamespace, localService); try { QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, - localNamespace, localService, 1, labels, request.getRequestURI()); + localNamespace, localService, 1, arguments, request.getRequestURI()); if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { if (!Objects.isNull(polarisRateLimiterLimitedFallback)) { @@ -122,6 +113,11 @@ 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 @@ -140,40 +136,4 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); } - private Map<String, String> getRequestLabels(HttpServletRequest request, String localNamespace, String localService) { - Map<String, String> labels = new HashMap<>(); - - // add build in labels - String path = request.getRequestURI(); - if (StringUtils.isNotBlank(path)) { - labels.put(LABEL_METHOD, path); - } - - // add rule expression labels - Map<String, String> expressionLabels = getRuleExpressionLabels(request, localNamespace, localService); - labels.putAll(expressionLabels); - - // add custom resolved labels - Map<String, String> customLabels = getCustomResolvedLabels(request); - labels.putAll(customLabels); - - return labels; - } - - private Map<String, String> getCustomResolvedLabels(HttpServletRequest request) { - if (labelResolver != null) { - try { - return labelResolver.resolve(request); - } - catch (Throwable e) { - LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e); - } - } - return Collections.emptyMap(); - } - - private Map<String, String> getRuleExpressionLabels(HttpServletRequest request, String namespace, String service) { - Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service); - return ServletExpressionLabelUtils.resolve(request, expressionLabels); - } } diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/resolver/RateLimitRuleArgumentReactiveResolver.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/resolver/RateLimitRuleArgumentReactiveResolver.java new file mode 100644 index 000000000..13c57d817 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/resolver/RateLimitRuleArgumentReactiveResolver.java @@ -0,0 +1,128 @@ +/* + * 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.ratelimit.resolver; + +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.polaris.context.ServiceRuleManager; +import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver; +import com.tencent.polaris.ratelimit.api.rpc.Argument; +import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.util.CollectionUtils; +import org.springframework.web.server.ServerWebExchange; + +import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAME; +import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE; + +/** + * resolve arguments from rate limit rule for Reactive. + * + * @author seansyyu 2023-03-09 + */ +public class RateLimitRuleArgumentReactiveResolver { + + private static final Logger LOG = LoggerFactory.getLogger(RateLimitRuleArgumentReactiveResolver.class); + + private final ServiceRuleManager serviceRuleManager; + + private final PolarisRateLimiterLabelReactiveResolver labelResolver; + + public RateLimitRuleArgumentReactiveResolver(ServiceRuleManager serviceRuleManager, PolarisRateLimiterLabelReactiveResolver labelResolver) { + this.serviceRuleManager = serviceRuleManager; + this.labelResolver = labelResolver; + } + + public Set<Argument> getArguments(ServerWebExchange request, String namespace, String service) { + RateLimitProto.RateLimit rateLimitRule = serviceRuleManager.getServiceRateLimitRule(namespace, service); + if (rateLimitRule == null) { + return Collections.emptySet(); + } + List<RateLimitProto.Rule> rules = rateLimitRule.getRulesList(); + if (CollectionUtils.isEmpty(rules)) { + return Collections.emptySet(); + } + return rules.stream() + .flatMap(rule -> rule.getArgumentsList().stream()) + .map(matchArgument -> { + String matchKey = matchArgument.getKey(); + Argument argument = null; + switch (matchArgument.getType()) { + case CUSTOM: + argument = StringUtils.isBlank(matchKey) ? null : + Argument.buildCustom(matchKey, Optional.ofNullable(getCustomResolvedLabels(request).get(matchKey)) + .orElse(StringUtils.EMPTY)); + break; + case METHOD: + argument = Argument.buildMethod(request.getRequest().getMethodValue()); + break; + case HEADER: + argument = StringUtils.isBlank(matchKey) ? null : + Argument.buildHeader(matchKey, Optional.ofNullable(request.getRequest().getHeaders() + .getFirst(matchKey)).orElse(StringUtils.EMPTY)); + break; + case QUERY: + argument = StringUtils.isBlank(matchKey) ? null : + Argument.buildQuery(matchKey, Optional.ofNullable(request.getRequest().getQueryParams() + .getFirst(matchKey)).orElse(StringUtils.EMPTY)); + break; + case CALLER_SERVICE: + String sourceServiceNamespace = MetadataContextHolder.getDisposableMetadata(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE, true) + .orElse(StringUtils.EMPTY); + String sourceServiceName = MetadataContextHolder.getDisposableMetadata(DEFAULT_METADATA_SOURCE_SERVICE_NAME, true) + .orElse(StringUtils.EMPTY); + if (!StringUtils.isEmpty(sourceServiceNamespace) && !StringUtils.isEmpty(sourceServiceName)) { + argument = Argument.buildCallerService(sourceServiceNamespace, sourceServiceName); + } + break; + case CALLER_IP: + InetSocketAddress remoteAddress = request.getRequest().getRemoteAddress(); + argument = Argument.buildCallerIP(remoteAddress != null ? remoteAddress.getAddress() + .getHostAddress() : StringUtils.EMPTY); + break; + default: + break; + } + return argument; + }).filter(Objects::nonNull).collect(Collectors.toSet()); + } + + private Map<String, String> getCustomResolvedLabels(ServerWebExchange request) { + if (labelResolver != null) { + try { + return labelResolver.resolve(request); + } + catch (Throwable e) { + LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e); + } + } + return Collections.emptyMap(); + } +} diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/resolver/RateLimitRuleArgumentServletResolver.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/resolver/RateLimitRuleArgumentServletResolver.java new file mode 100644 index 000000000..e384d0333 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/resolver/RateLimitRuleArgumentServletResolver.java @@ -0,0 +1,123 @@ +/* + * 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.ratelimit.resolver; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.servlet.http.HttpServletRequest; + +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.polaris.context.ServiceRuleManager; +import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter; +import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver; +import com.tencent.polaris.ratelimit.api.rpc.Argument; +import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.util.CollectionUtils; + +import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAME; +import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE; + +/** + * resolve arguments from rate limit rule for Servlet. + * + * @author seansyyu 2023-03-09 + */ +public class RateLimitRuleArgumentServletResolver { + + private static final Logger LOG = LoggerFactory.getLogger(QuotaCheckServletFilter.class); + + private final ServiceRuleManager serviceRuleManager; + + private final PolarisRateLimiterLabelServletResolver labelResolver; + + public RateLimitRuleArgumentServletResolver(ServiceRuleManager serviceRuleManager, PolarisRateLimiterLabelServletResolver labelResolver) { + this.serviceRuleManager = serviceRuleManager; + this.labelResolver = labelResolver; + } + + public Set<Argument> getArguments(HttpServletRequest request, String namespace, String service) { + RateLimitProto.RateLimit rateLimitRule = serviceRuleManager.getServiceRateLimitRule(namespace, service); + if (rateLimitRule == null) { + return Collections.emptySet(); + } + List<RateLimitProto.Rule> rules = rateLimitRule.getRulesList(); + if (CollectionUtils.isEmpty(rules)) { + return Collections.emptySet(); + } + return rules.stream() + .flatMap(rule -> rule.getArgumentsList().stream()) + .map(matchArgument -> { + String matchKey = matchArgument.getKey(); + Argument argument = null; + switch (matchArgument.getType()) { + case CUSTOM: + argument = StringUtils.isBlank(matchKey) ? null : + Argument.buildCustom(matchKey, Optional.ofNullable(getCustomResolvedLabels(request).get(matchKey)).orElse(StringUtils.EMPTY)); + break; + case METHOD: + argument = Argument.buildMethod(request.getMethod()); + break; + case HEADER: + argument = StringUtils.isBlank(matchKey) ? null : + Argument.buildHeader(matchKey, Optional.ofNullable(request.getHeader(matchKey)).orElse(StringUtils.EMPTY)); + break; + case QUERY: + argument = StringUtils.isBlank(matchKey) ? null : + Argument.buildQuery(matchKey, Optional.ofNullable(request.getParameter(matchKey)).orElse(StringUtils.EMPTY)); + break; + case CALLER_SERVICE: + String sourceServiceNamespace = MetadataContextHolder.getDisposableMetadata(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE, true).orElse(StringUtils.EMPTY); + String sourceServiceName = MetadataContextHolder.getDisposableMetadata(DEFAULT_METADATA_SOURCE_SERVICE_NAME, true).orElse(StringUtils.EMPTY); + if (!StringUtils.isEmpty(sourceServiceNamespace) && !StringUtils.isEmpty(sourceServiceName)) { + argument = Argument.buildCallerService(sourceServiceNamespace, sourceServiceName); + } + break; + case CALLER_IP: + argument = Argument.buildCallerIP(Optional.ofNullable(request.getRemoteAddr()).orElse(StringUtils.EMPTY)); + break; + default: + break; + } + return argument; + }).filter(Objects::nonNull).collect(Collectors.toSet()); + } + + private Map<String, String> getCustomResolvedLabels(HttpServletRequest request) { + if (labelResolver != null) { + try { + return labelResolver.resolve(request); + } + catch (Throwable e) { + LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e); + } + } + return Collections.emptyMap(); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/utils/QuotaCheckUtils.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/utils/QuotaCheckUtils.java index d7e4aaae6..3adcee5e4 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/utils/QuotaCheckUtils.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/utils/QuotaCheckUtils.java @@ -18,9 +18,11 @@ package com.tencent.cloud.polaris.ratelimit.utils; import java.util.Map; +import java.util.Set; import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult; import com.tencent.polaris.ratelimit.api.core.LimitAPI; +import com.tencent.polaris.ratelimit.api.rpc.Argument; import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; import org.slf4j.Logger; @@ -38,8 +40,9 @@ public final class QuotaCheckUtils { private QuotaCheckUtils() { } - public static QuotaResponse getQuota(LimitAPI limitAPI, String namespace, - String service, int count, Map<String, String> labels, String method) { + @Deprecated + public static QuotaResponse getQuota(LimitAPI limitAPI, String namespace, String service, int count, + Map<String, String> labels, String method) { // build quota request QuotaRequest quotaRequest = new QuotaRequest(); quotaRequest.setNamespace(namespace); @@ -52,10 +55,27 @@ public final class QuotaCheckUtils { return limitAPI.getQuota(quotaRequest); } catch (Throwable throwable) { - LOG.error("fail to invoke getQuota of LimitAPI with QuotaRequest[{}].", - quotaRequest, throwable); - return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, - "get quota failed")); + LOG.error("fail to invoke getQuota of LimitAPI with QuotaRequest[{}].", quotaRequest, throwable); + return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "get quota failed")); + } + } + + public static QuotaResponse getQuota(LimitAPI limitAPI, String namespace, String service, int count, + Set<Argument> arguments, String method) { + // build quota request + QuotaRequest quotaRequest = new QuotaRequest(); + quotaRequest.setNamespace(namespace); + quotaRequest.setService(service); + quotaRequest.setCount(count); + quotaRequest.setArguments(arguments); + quotaRequest.setMethod(method); + + try { + return limitAPI.getQuota(quotaRequest); + } + catch (Throwable throwable) { + LOG.error("fail to invoke getQuota of LimitAPI with QuotaRequest[{}].", quotaRequest, throwable); + return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "get quota failed")); } } } diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitAutoConfigurationTest.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitAutoConfigurationTest.java index 54fafa35c..c21a71f3e 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitAutoConfigurationTest.java @@ -18,9 +18,10 @@ package com.tencent.cloud.polaris.ratelimit.config; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; -import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter; +import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentReactiveResolver; +import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServletResolver; import com.tencent.polaris.ratelimit.api.core.LimitAPI; import org.junit.jupiter.api.Test; @@ -43,7 +44,8 @@ public class PolarisRateLimitAutoConfigurationTest { private final WebApplicationContextRunner webApplicationContextRunner = new WebApplicationContextRunner(); - private final ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = new ReactiveWebApplicationContextRunner(); + private final ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = + new ReactiveWebApplicationContextRunner(); @Test public void testNoWebApplication() { @@ -54,12 +56,12 @@ public class PolarisRateLimitAutoConfigurationTest { PolarisRateLimitAutoConfiguration.class)) .run(context -> { assertThat(context).hasSingleBean(LimitAPI.class); - assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class); + assertThat(context).doesNotHaveBean(RateLimitRuleArgumentServletResolver.class); + assertThat(context).doesNotHaveBean(RateLimitRuleArgumentReactiveResolver.class); assertThat(context).doesNotHaveBean(PolarisRateLimitAutoConfiguration.QuotaCheckFilterConfig.class); assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class); assertThat(context).doesNotHaveBean(FilterRegistrationBean.class); - assertThat(context).doesNotHaveBean( - PolarisRateLimitAutoConfiguration.MetadataReactiveFilterConfig.class); + assertThat(context).doesNotHaveBean(PolarisRateLimitAutoConfiguration.MetadataReactiveFilterConfig.class); assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class); }); } @@ -67,37 +69,35 @@ public class PolarisRateLimitAutoConfigurationTest { @Test public void testServletWebApplication() { this.webApplicationContextRunner - .withConfiguration(AutoConfigurations.of( - PolarisContextAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(PolarisContextAutoConfiguration.class, PolarisRateLimitProperties.class, PolarisRateLimitAutoConfiguration.class)) .run(context -> { assertThat(context).hasSingleBean(LimitAPI.class); - assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class); + assertThat(context).hasSingleBean(RateLimitRuleArgumentServletResolver.class); assertThat(context).hasSingleBean(PolarisRateLimitAutoConfiguration.QuotaCheckFilterConfig.class); assertThat(context).hasSingleBean(QuotaCheckServletFilter.class); assertThat(context).hasSingleBean(FilterRegistrationBean.class); - assertThat(context).doesNotHaveBean( - PolarisRateLimitAutoConfiguration.MetadataReactiveFilterConfig.class); + assertThat(context).doesNotHaveBean(PolarisRateLimitAutoConfiguration.MetadataReactiveFilterConfig.class); assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class); + assertThat(context).doesNotHaveBean(RateLimitRuleArgumentReactiveResolver.class); }); } @Test public void testReactiveWebApplication() { this.reactiveWebApplicationContextRunner - .withConfiguration(AutoConfigurations.of( - PolarisContextAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(PolarisContextAutoConfiguration.class, PolarisRateLimitProperties.class, PolarisRateLimitAutoConfiguration.class)) .run(context -> { assertThat(context).hasSingleBean(LimitAPI.class); - assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class); + assertThat(context).doesNotHaveBean(RateLimitRuleArgumentServletResolver.class); + assertThat(context).hasSingleBean(RateLimitRuleArgumentReactiveResolver.class); assertThat(context).doesNotHaveBean(PolarisRateLimitAutoConfiguration.QuotaCheckFilterConfig.class); assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class); assertThat(context).doesNotHaveBean(FilterRegistrationBean.class); - assertThat(context).hasSingleBean( - PolarisRateLimitAutoConfiguration.MetadataReactiveFilterConfig.class); + assertThat(context).hasSingleBean(PolarisRateLimitAutoConfiguration.MetadataReactiveFilterConfig.class); assertThat(context).hasSingleBean(QuotaCheckReactiveFilter.class); }); } diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitPropertiesTest.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitPropertiesTest.java index e36b9d530..3b27fe84a 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitPropertiesTest.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitPropertiesTest.java @@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Haotian Zhang */ @ExtendWith(SpringExtension.class) -@SpringBootTest(classes = PolarisRateLimitPropertiesTest.TestApplication.class) +@SpringBootTest(classes = PolarisRateLimitPropertiesTest.TestApplication.class, properties = "spring.application.name=test") @ActiveProfiles("test") public class PolarisRateLimitPropertiesTest { 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 3d3c3c62e..95e2a0103 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 @@ -17,32 +17,32 @@ package com.tencent.cloud.polaris.ratelimit.filter; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; import java.util.Collections; -import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; import com.tencent.cloud.common.metadata.MetadataContext; -import com.tencent.cloud.common.util.ApplicationContextAwareUtils; -import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; -import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; +import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; +import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentReactiveResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult; import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.MockedStatic; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; @@ -60,10 +60,8 @@ import org.springframework.web.server.WebFilterChain; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; /** @@ -76,36 +74,15 @@ import static org.mockito.Mockito.when; @SpringBootTest(classes = QuotaCheckReactiveFilterTest.TestApplication.class, properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"}) public class QuotaCheckReactiveFilterTest { - - private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; - private static MockedStatic<SpringWebExpressionLabelUtils> expressionLabelUtilsMockedStatic; private final PolarisRateLimiterLabelReactiveResolver labelResolver = - exchange -> Collections.singletonMap("ReactiveResolver", "ReactiveResolver"); + exchange -> Collections.singletonMap("xxx", "xxx"); private QuotaCheckReactiveFilter quotaCheckReactiveFilter; private QuotaCheckReactiveFilter quotaCheckWithRateLimiterLimitedFallbackReactiveFilter; private PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback; - @BeforeAll - static void beforeAll() { - expressionLabelUtilsMockedStatic = mockStatic(SpringWebExpressionLabelUtils.class); - when(SpringWebExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())) - .thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver")); - - mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); - mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) - .thenReturn("unit-test"); - } - - @AfterAll - static void afterAll() { - mockedApplicationContextAwareUtils.close(); - expressionLabelUtilsMockedStatic.close(); - } - @BeforeEach - void setUp() { + void setUp() throws InvalidProtocolBufferException { MetadataContext.LOCAL_NAMESPACE = "TEST"; - polarisRateLimiterLimitedFallback = new JsonPolarisRateLimiterLimitedFallback(); LimitAPI limitAPI = mock(LimitAPI.class); when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> { @@ -117,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)); } }); @@ -128,14 +107,26 @@ public class QuotaCheckReactiveFilterTest { polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips提示消息"); polarisRateLimitProperties.setRejectHttpCode(419); - RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class); - when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())) - .thenReturn(Collections.emptySet()); - - this.quotaCheckReactiveFilter = new QuotaCheckReactiveFilter( - limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver, null); - this.quotaCheckWithRateLimiterLimitedFallbackReactiveFilter = new QuotaCheckReactiveFilter( - limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver, polarisRateLimiterLimitedFallback); + PolarisRateLimitProperties polarisRateLimitWithHtmlRejectTipsProperties = new PolarisRateLimitProperties(); + polarisRateLimitWithHtmlRejectTipsProperties.setRejectRequestTips("<h1>RejectRequestTips提示消息</h1>"); + polarisRateLimitWithHtmlRejectTipsProperties.setRejectHttpCode(419); + + ServiceRuleManager serviceRuleManager = mock(ServiceRuleManager.class); + + RateLimitProto.Rule.Builder ratelimitRuleBuilder = RateLimitProto.Rule.newBuilder(); + InputStream inputStream = QuotaCheckServletFilterTest.class.getClassLoader() + .getResourceAsStream("ratelimit.json"); + String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines() + .collect(Collectors.joining("")); + JsonFormat.parser().ignoringUnknownFields().merge(json, ratelimitRuleBuilder); + RateLimitProto.Rule rateLimitRule = ratelimitRuleBuilder.build(); + RateLimitProto.RateLimit rateLimit = RateLimitProto.RateLimit.newBuilder().addRules(rateLimitRule).build(); + when(serviceRuleManager.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit); + + RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentReactiveResolver = new RateLimitRuleArgumentReactiveResolver(serviceRuleManager, labelResolver); + this.quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(limitAPI, polarisRateLimitProperties, rateLimitRuleArgumentReactiveResolver, null); + this.polarisRateLimiterLimitedFallback = new JsonPolarisRateLimiterLimitedFallback(); + this.quotaCheckWithRateLimiterLimitedFallbackReactiveFilter = new QuotaCheckReactiveFilter(limitAPI, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleArgumentReactiveResolver, polarisRateLimiterLimitedFallback); } @Test @@ -156,42 +147,6 @@ public class QuotaCheckReactiveFilterTest { } } - @Test - public void testGetRuleExpressionLabels() { - try { - Method getCustomResolvedLabels = - QuotaCheckReactiveFilter.class.getDeclaredMethod("getCustomResolvedLabels", ServerWebExchange.class); - getCustomResolvedLabels.setAccessible(true); - - // Mock request - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/test").build(); - ServerWebExchange exchange = MockServerWebExchange.from(request); - - // labelResolver != null - Map<String, String> result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange); - assertThat(result.size()).isEqualTo(1); - assertThat(result.get("ReactiveResolver")).isEqualTo("ReactiveResolver"); - - // throw exception - PolarisRateLimiterLabelReactiveResolver exceptionLabelResolver = exchange1 -> { - throw new RuntimeException("Mock exception."); - }; - quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(null, exceptionLabelResolver, null, null, null); - result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange); - assertThat(result.size()).isEqualTo(0); - - // labelResolver == null - quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(null, null, null, null, null); - result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange); - assertThat(result.size()).isEqualTo(0); - - getCustomResolvedLabels.setAccessible(false); - } - catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - fail("Exception encountered.", e); - } - } - @Test public void testFilter() { // Create mock WebFilterChain @@ -199,19 +154,20 @@ public class QuotaCheckReactiveFilterTest { // Mock request MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/test").build(); - ServerWebExchange exchange = MockServerWebExchange.from(request); quotaCheckReactiveFilter.init(); // Pass MetadataContext.LOCAL_SERVICE = "TestApp1"; - quotaCheckReactiveFilter.filter(exchange, webFilterChain); + ServerWebExchange testApp1Exchange = MockServerWebExchange.from(request); + quotaCheckReactiveFilter.filter(testApp1Exchange, webFilterChain); // Unirate waiting 1000ms MetadataContext.LOCAL_SERVICE = "TestApp2"; + ServerWebExchange testApp2Exchange = MockServerWebExchange.from(request); long startTimestamp = System.currentTimeMillis(); CountDownLatch countDownLatch = new CountDownLatch(1); - quotaCheckReactiveFilter.filter(exchange, webFilterChain).subscribe(e -> { + quotaCheckReactiveFilter.filter(testApp2Exchange, webFilterChain).subscribe(e -> { }, t -> { }, countDownLatch::countDown); try { @@ -224,14 +180,16 @@ public class QuotaCheckReactiveFilterTest { // Rate limited MetadataContext.LOCAL_SERVICE = "TestApp3"; - quotaCheckReactiveFilter.filter(exchange, webFilterChain); - ServerHttpResponse response = exchange.getResponse(); + ServerWebExchange testApp3Exchange = MockServerWebExchange.from(request); + quotaCheckReactiveFilter.filter(testApp3Exchange, webFilterChain); + ServerHttpResponse response = testApp3Exchange.getResponse(); assertThat(response.getRawStatusCode()).isEqualTo(419); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE); // Exception MetadataContext.LOCAL_SERVICE = "TestApp4"; - quotaCheckReactiveFilter.filter(exchange, webFilterChain); + ServerWebExchange testApp4Exchange = MockServerWebExchange.from(request); + quotaCheckReactiveFilter.filter(testApp4Exchange, webFilterChain); } @Test 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 81f3cd2cb..1b747699e 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 @@ -17,53 +17,47 @@ package com.tencent.cloud.polaris.ratelimit.filter; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; import java.util.Collections; -import java.util.Map; +import java.util.stream.Collectors; import javax.servlet.FilterChain; import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; import com.tencent.cloud.common.metadata.MetadataContext; -import com.tencent.cloud.common.util.ApplicationContextAwareUtils; -import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; -import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; +import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; +import com.tencent.cloud.polaris.ratelimit.resolver.RateLimitRuleArgumentServletResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLimitedFallback; import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult; import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.web.server.ServerWebExchange; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; /** @@ -71,41 +65,23 @@ import static org.mockito.Mockito.when; * * @author Haotian Zhang, cheese8 */ -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -@SpringBootTest(classes = QuotaCheckServletFilterTest.TestApplication.class, properties = { - "spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp" -}) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = QuotaCheckServletFilterTest.TestApplication.class, + properties = { + "spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp" + }) public class QuotaCheckServletFilterTest { - private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; - private static MockedStatic<SpringWebExpressionLabelUtils> expressionLabelUtilsMockedStatic; - private PolarisRateLimiterLabelServletResolver labelResolver = - exchange -> Collections.singletonMap("ServletResolver", "ServletResolver"); + private final PolarisRateLimiterLabelServletResolver labelResolver = + exchange -> Collections.singletonMap("xxx", "xxx"); private QuotaCheckServletFilter quotaCheckServletFilter; private QuotaCheckServletFilter quotaCheckWithHtmlRejectTipsServletFilter; private QuotaCheckServletFilter quotaCheckWithRateLimiterLimitedFallbackFilter; private PolarisRateLimiterLimitedFallback polarisRateLimiterLimitedFallback; - @BeforeAll - static void beforeAll() { - expressionLabelUtilsMockedStatic = mockStatic(SpringWebExpressionLabelUtils.class); - when(SpringWebExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())) - .thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver")); - - mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); - mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) - .thenReturn("unit-test"); - } - - @AfterAll - static void afterAll() { - mockedApplicationContextAwareUtils.close(); - expressionLabelUtilsMockedStatic.close(); - } - @BeforeEach - void setUp() { + void setUp() throws InvalidProtocolBufferException { MetadataContext.LOCAL_NAMESPACE = "TEST"; LimitAPI limitAPI = mock(LimitAPI.class); @@ -118,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)); @@ -133,15 +111,23 @@ public class QuotaCheckServletFilterTest { polarisRateLimitWithHtmlRejectTipsProperties.setRejectRequestTips("<h1>RejectRequestTips提示消息</h1>"); polarisRateLimitWithHtmlRejectTipsProperties.setRejectHttpCode(419); - RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class); - when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.emptySet()); - - this.quotaCheckServletFilter = new QuotaCheckServletFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver, null); - this.quotaCheckWithHtmlRejectTipsServletFilter = new QuotaCheckServletFilter( - limitAPI, labelResolver, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleLabelResolver, null); - polarisRateLimiterLimitedFallback = new JsonPolarisRateLimiterLimitedFallback(); - this.quotaCheckWithRateLimiterLimitedFallbackFilter = new QuotaCheckServletFilter( - limitAPI, labelResolver, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleLabelResolver, polarisRateLimiterLimitedFallback); + ServiceRuleManager serviceRuleManager = mock(ServiceRuleManager.class); + + RateLimitProto.Rule.Builder ratelimitRuleBuilder = RateLimitProto.Rule.newBuilder(); + InputStream inputStream = QuotaCheckServletFilterTest.class.getClassLoader() + .getResourceAsStream("ratelimit.json"); + String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines() + .collect(Collectors.joining("")); + JsonFormat.parser().ignoringUnknownFields().merge(json, ratelimitRuleBuilder); + RateLimitProto.Rule rateLimitRule = ratelimitRuleBuilder.build(); + RateLimitProto.RateLimit rateLimit = RateLimitProto.RateLimit.newBuilder().addRules(rateLimitRule).build(); + when(serviceRuleManager.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit); + + RateLimitRuleArgumentServletResolver rateLimitRuleArgumentServletResolver = new RateLimitRuleArgumentServletResolver(serviceRuleManager, labelResolver); + this.quotaCheckServletFilter = new QuotaCheckServletFilter(limitAPI, polarisRateLimitProperties, rateLimitRuleArgumentServletResolver, null); + this.quotaCheckWithHtmlRejectTipsServletFilter = new QuotaCheckServletFilter(limitAPI, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleArgumentServletResolver, null); + this.polarisRateLimiterLimitedFallback = new JsonPolarisRateLimiterLimitedFallback(); + this.quotaCheckWithRateLimiterLimitedFallbackFilter = new QuotaCheckServletFilter(limitAPI, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleArgumentServletResolver, polarisRateLimiterLimitedFallback); } @Test @@ -167,40 +153,6 @@ public class QuotaCheckServletFilterTest { quotaCheckWithRateLimiterLimitedFallbackFilter.init(); } - @Test - public void testGetRuleExpressionLabels() { - try { - Method getCustomResolvedLabels = QuotaCheckServletFilter.class.getDeclaredMethod("getCustomResolvedLabels", HttpServletRequest.class); - getCustomResolvedLabels.setAccessible(true); - - // Mock request - MockHttpServletRequest request = new MockHttpServletRequest(); - - // labelResolver != null - Map<String, String> result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request); - assertThat(result.size()).isEqualTo(1); - assertThat(result.get("ServletResolver")).isEqualTo("ServletResolver"); - - // throw exception - PolarisRateLimiterLabelServletResolver exceptionLabelResolver = request1 -> { - throw new RuntimeException("Mock exception."); - }; - quotaCheckServletFilter = new QuotaCheckServletFilter(null, exceptionLabelResolver, null, null, null); - result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request); - assertThat(result.size()).isEqualTo(0); - - // labelResolver == null - quotaCheckServletFilter = new QuotaCheckServletFilter(null, null, null, null, null); - result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request); - assertThat(result.size()).isEqualTo(0); - - getCustomResolvedLabels.setAccessible(false); - } - catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - fail("Exception encountered.", e); - } - } - @Test public void testDoFilterInternal() { // Create mock FilterChain @@ -210,34 +162,39 @@ public class QuotaCheckServletFilterTest { // Mock request MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); quotaCheckServletFilter.init(); + quotaCheckWithHtmlRejectTipsServletFilter.init(); try { // Pass MetadataContext.LOCAL_SERVICE = "TestApp1"; - quotaCheckServletFilter.doFilterInternal(request, response, filterChain); + MockHttpServletResponse testApp1Response = new MockHttpServletResponse(); + quotaCheckServletFilter.doFilterInternal(request, testApp1Response, filterChain); // Unirate waiting 1000ms MetadataContext.LOCAL_SERVICE = "TestApp2"; + MockHttpServletResponse testApp2Response = new MockHttpServletResponse(); long startTimestamp = System.currentTimeMillis(); - quotaCheckServletFilter.doFilterInternal(request, response, filterChain); + quotaCheckServletFilter.doFilterInternal(request, testApp2Response, filterChain); assertThat(System.currentTimeMillis() - startTimestamp).isGreaterThanOrEqualTo(1000L); // Rate limited MetadataContext.LOCAL_SERVICE = "TestApp3"; - quotaCheckServletFilter.doFilterInternal(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(419); - assertThat(response.getContentAsString()).isEqualTo("RejectRequestTips提示消息"); + MockHttpServletResponse testApp3Response = new MockHttpServletResponse(); + quotaCheckServletFilter.doFilterInternal(request, testApp3Response, filterChain); + assertThat(testApp3Response.getStatus()).isEqualTo(419); + assertThat(testApp3Response.getContentAsString()).isEqualTo("RejectRequestTips提示消息"); - quotaCheckWithHtmlRejectTipsServletFilter.doFilterInternal(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(419); - assertThat(response.getContentAsString()).isEqualTo("RejectRequestTips提示消息"); + MockHttpServletResponse testApp3Response2 = new MockHttpServletResponse(); + quotaCheckWithHtmlRejectTipsServletFilter.doFilterInternal(request, testApp3Response2, filterChain); + assertThat(testApp3Response2.getStatus()).isEqualTo(419); + assertThat(testApp3Response2.getContentAsString()).isEqualTo("<h1>RejectRequestTips提示消息</h1>"); // Exception + MockHttpServletResponse testApp4Response = new MockHttpServletResponse(); MetadataContext.LOCAL_SERVICE = "TestApp4"; - quotaCheckServletFilter.doFilterInternal(request, response, filterChain); + quotaCheckServletFilter.doFilterInternal(request, testApp4Response, filterChain); } catch (ServletException | IOException e) { fail("Exception encountered.", e); @@ -252,31 +209,34 @@ public class QuotaCheckServletFilterTest { // Mock request MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); quotaCheckWithRateLimiterLimitedFallbackFilter.init(); try { // Pass MetadataContext.LOCAL_SERVICE = "TestApp1"; - quotaCheckWithRateLimiterLimitedFallbackFilter.doFilterInternal(request, response, filterChain); + MockHttpServletResponse testApp1response = new MockHttpServletResponse(); + quotaCheckWithRateLimiterLimitedFallbackFilter.doFilterInternal(request, testApp1response, filterChain); // Unirate waiting 1000ms MetadataContext.LOCAL_SERVICE = "TestApp2"; + MockHttpServletResponse testApp2response = new MockHttpServletResponse(); long startTimestamp = System.currentTimeMillis(); - quotaCheckWithRateLimiterLimitedFallbackFilter.doFilterInternal(request, response, filterChain); + quotaCheckWithRateLimiterLimitedFallbackFilter.doFilterInternal(request, testApp2response, filterChain); assertThat(System.currentTimeMillis() - startTimestamp).isGreaterThanOrEqualTo(1000L); // Rate limited MetadataContext.LOCAL_SERVICE = "TestApp3"; + MockHttpServletResponse testApp3response = new MockHttpServletResponse(); String contentType = new MediaType(polarisRateLimiterLimitedFallback.mediaType(), polarisRateLimiterLimitedFallback.charset()).toString(); - quotaCheckWithRateLimiterLimitedFallbackFilter.doFilterInternal(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(polarisRateLimiterLimitedFallback.rejectHttpCode()); - assertThat(response.getContentAsString()).isEqualTo(polarisRateLimiterLimitedFallback.rejectTips()); - assertThat(response.getContentType()).isEqualTo(contentType); + quotaCheckWithRateLimiterLimitedFallbackFilter.doFilterInternal(request, testApp3response, filterChain); + assertThat(testApp3response.getStatus()).isEqualTo(polarisRateLimiterLimitedFallback.rejectHttpCode()); + assertThat(testApp3response.getContentAsString()).isEqualTo(polarisRateLimiterLimitedFallback.rejectTips()); + assertThat(testApp3response.getContentType()).isEqualTo(contentType); // Exception MetadataContext.LOCAL_SERVICE = "TestApp4"; - quotaCheckWithRateLimiterLimitedFallbackFilter.doFilterInternal(request, response, filterChain); + MockHttpServletResponse testApp4response = new MockHttpServletResponse(); + quotaCheckWithRateLimiterLimitedFallbackFilter.doFilterInternal(request, testApp4response, filterChain); } catch (ServletException | IOException e) { fail("Exception encountered.", e); diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/resolver/RateLimitRuleArgumentReactiveResolverTest.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/resolver/RateLimitRuleArgumentReactiveResolverTest.java new file mode 100644 index 000000000..1648a9618 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/resolver/RateLimitRuleArgumentReactiveResolverTest.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.polaris.ratelimit.resolver; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.polaris.context.ServiceRuleManager; +import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilterTest; +import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver; +import com.tencent.polaris.ratelimit.api.rpc.Argument; +import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.server.ServerWebExchange; + +import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAME; +import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = RateLimitRuleArgumentReactiveResolverTest.TestApplication.class, + properties = { + "spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp" + }) +public class RateLimitRuleArgumentReactiveResolverTest { + + private final PolarisRateLimiterLabelReactiveResolver labelResolver = + exchange -> Collections.singletonMap("xxx", "xxx"); + + private final PolarisRateLimiterLabelReactiveResolver labelResolverEx = + exchange -> { + throw new RuntimeException(); + }; + + private RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentReactiveResolver1; + private RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentReactiveResolver2; + private RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentReactiveResolver3; + private RateLimitRuleArgumentReactiveResolver rateLimitRuleArgumentReactiveResolver4; + + @BeforeEach + void setUp() throws InvalidProtocolBufferException { + MetadataContext.LOCAL_NAMESPACE = "TEST"; + + ServiceRuleManager serviceRuleManager = mock(ServiceRuleManager.class); + + RateLimitProto.Rule.Builder ratelimitRuleBuilder = RateLimitProto.Rule.newBuilder(); + InputStream inputStream = QuotaCheckServletFilterTest.class.getClassLoader().getResourceAsStream("ratelimit.json"); + String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("")); + JsonFormat.parser().ignoringUnknownFields().merge(json, ratelimitRuleBuilder); + RateLimitProto.Rule rateLimitRule = ratelimitRuleBuilder.build(); + RateLimitProto.RateLimit rateLimit = RateLimitProto.RateLimit.newBuilder().addRules(rateLimitRule).build(); + when(serviceRuleManager.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit); + + // normal + this.rateLimitRuleArgumentReactiveResolver1 = new RateLimitRuleArgumentReactiveResolver(serviceRuleManager, labelResolver); + // ex + this.rateLimitRuleArgumentReactiveResolver2 = new RateLimitRuleArgumentReactiveResolver(serviceRuleManager, labelResolverEx); + // null + ServiceRuleManager serviceRuleManager1 = mock(ServiceRuleManager.class); + when(serviceRuleManager1.getServiceRateLimitRule(anyString(), anyString())).thenReturn(null); + this.rateLimitRuleArgumentReactiveResolver3 = new RateLimitRuleArgumentReactiveResolver(serviceRuleManager1, labelResolver); + // null 2 + ServiceRuleManager serviceRuleManager2 = mock(ServiceRuleManager.class); + RateLimitProto.RateLimit rateLimit2 = RateLimitProto.RateLimit.newBuilder().build(); + when(serviceRuleManager2.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit2); + this.rateLimitRuleArgumentReactiveResolver4 = new RateLimitRuleArgumentReactiveResolver(serviceRuleManager2, labelResolver); + } + + @Test + public void testGetRuleArguments() { + // Mock request + MetadataContext.LOCAL_SERVICE = "Test"; + // Mock request + MockServerHttpRequest request = MockServerHttpRequest.get("http://127.0.0.1:8080/test") + .remoteAddress(new InetSocketAddress("127.0.0.1", 8080)) + .header("xxx", "xxx") + .queryParam("yyy", "yyy") + .build(); + ServerWebExchange exchange = MockServerWebExchange.from(request); + MetadataContext metadataContext = new MetadataContext(); + metadataContext.setUpstreamDisposableMetadata(new HashMap<String, String>() {{ + put(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE, MetadataContext.LOCAL_NAMESPACE); + put(DEFAULT_METADATA_SOURCE_SERVICE_NAME, MetadataContext.LOCAL_SERVICE); + }}); + MetadataContextHolder.set(metadataContext); + Set<Argument> arguments = rateLimitRuleArgumentReactiveResolver1.getArguments(exchange, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE); + Set<Argument> exceptRes = new HashSet<>(); + exceptRes.add(Argument.buildMethod("GET")); + exceptRes.add(Argument.buildHeader("xxx", "xxx")); + exceptRes.add(Argument.buildQuery("yyy", "yyy")); + exceptRes.add(Argument.buildCallerIP("127.0.0.1")); + exceptRes.add(Argument.buildCustom("xxx", "xxx")); + exceptRes.add(Argument.buildCallerService(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE)); + assertThat(arguments).isEqualTo(exceptRes); + + rateLimitRuleArgumentReactiveResolver2.getArguments(exchange, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE); + rateLimitRuleArgumentReactiveResolver3.getArguments(exchange, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE); + rateLimitRuleArgumentReactiveResolver4.getArguments(exchange, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE); + } + + @SpringBootApplication + protected static class TestApplication { + } + +} diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/resolver/RateLimitRuleArgumentServletResolverTest.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/resolver/RateLimitRuleArgumentServletResolverTest.java new file mode 100644 index 000000000..1a8b49ed7 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/resolver/RateLimitRuleArgumentServletResolverTest.java @@ -0,0 +1,135 @@ +/* + * 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.ratelimit.resolver; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.polaris.context.ServiceRuleManager; +import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilterTest; +import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver; +import com.tencent.polaris.ratelimit.api.rpc.Argument; +import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAME; +import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = RateLimitRuleArgumentServletResolverTest.TestApplication.class, + properties = { + "spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp" + }) +public class RateLimitRuleArgumentServletResolverTest { + + private final PolarisRateLimiterLabelServletResolver labelResolver = + exchange -> Collections.singletonMap("xxx", "xxx"); + private final PolarisRateLimiterLabelServletResolver labelResolverEx = + exchange -> { + throw new RuntimeException(); + }; + + private RateLimitRuleArgumentServletResolver rateLimitRuleArgumentServletResolver1; + private RateLimitRuleArgumentServletResolver rateLimitRuleArgumentServletResolver2; + private RateLimitRuleArgumentServletResolver rateLimitRuleArgumentServletResolver3; + private RateLimitRuleArgumentServletResolver rateLimitRuleArgumentServletResolver4; + + @BeforeEach + void setUp() throws InvalidProtocolBufferException { + MetadataContext.LOCAL_NAMESPACE = "TEST"; + + ServiceRuleManager serviceRuleManager = mock(ServiceRuleManager.class); + + RateLimitProto.Rule.Builder ratelimitRuleBuilder = RateLimitProto.Rule.newBuilder(); + InputStream inputStream = QuotaCheckServletFilterTest.class.getClassLoader().getResourceAsStream("ratelimit.json"); + String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("")); + JsonFormat.parser().ignoringUnknownFields().merge(json, ratelimitRuleBuilder); + RateLimitProto.Rule rateLimitRule = ratelimitRuleBuilder.build(); + RateLimitProto.RateLimit rateLimit = RateLimitProto.RateLimit.newBuilder().addRules(rateLimitRule).build(); + when(serviceRuleManager.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit); + + // normal + this.rateLimitRuleArgumentServletResolver1 = new RateLimitRuleArgumentServletResolver(serviceRuleManager, labelResolver); + // ex + this.rateLimitRuleArgumentServletResolver2 = new RateLimitRuleArgumentServletResolver(serviceRuleManager, labelResolverEx); + // null + ServiceRuleManager serviceRuleManager1 = mock(ServiceRuleManager.class); + when(serviceRuleManager1.getServiceRateLimitRule(anyString(), anyString())).thenReturn(null); + this.rateLimitRuleArgumentServletResolver3 = new RateLimitRuleArgumentServletResolver(serviceRuleManager1, labelResolver); + // null 2 + ServiceRuleManager serviceRuleManager2 = mock(ServiceRuleManager.class); + RateLimitProto.RateLimit rateLimit2 = RateLimitProto.RateLimit.newBuilder().build(); + when(serviceRuleManager2.getServiceRateLimitRule(anyString(), anyString())).thenReturn(rateLimit2); + this.rateLimitRuleArgumentServletResolver4 = new RateLimitRuleArgumentServletResolver(serviceRuleManager2, labelResolver); + } + + @Test + public void testGetRuleArguments() { + // Mock request + MetadataContext.LOCAL_SERVICE = "Test"; + MockHttpServletRequest request = new MockHttpServletRequest(null, "GET", "/xxx"); + request.setParameter("yyy", "yyy"); + request.addHeader("xxx", "xxx"); + MetadataContext metadataContext = new MetadataContext(); + metadataContext.setUpstreamDisposableMetadata(new HashMap<String, String>() {{ + put(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE, MetadataContext.LOCAL_NAMESPACE); + put(DEFAULT_METADATA_SOURCE_SERVICE_NAME, MetadataContext.LOCAL_SERVICE); + }}); + MetadataContextHolder.set(metadataContext); + Set<Argument> arguments = rateLimitRuleArgumentServletResolver1.getArguments(request, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE); + Set<Argument> exceptRes = new HashSet<>(); + exceptRes.add(Argument.buildMethod("GET")); + exceptRes.add(Argument.buildHeader("xxx", "xxx")); + exceptRes.add(Argument.buildQuery("yyy", "yyy")); + exceptRes.add(Argument.buildCallerIP("127.0.0.1")); + exceptRes.add(Argument.buildCustom("xxx", "xxx")); + exceptRes.add(Argument.buildCallerService(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE)); + assertThat(arguments).isEqualTo(exceptRes); + + rateLimitRuleArgumentServletResolver2.getArguments(request, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE); + rateLimitRuleArgumentServletResolver3.getArguments(request, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE); + rateLimitRuleArgumentServletResolver4.getArguments(request, MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE); + } + + @SpringBootApplication + protected static class TestApplication { + } +} diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/utils/QuotaCheckUtilsTest.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/utils/QuotaCheckUtilsTest.java index 91e361a8f..ad2691f4d 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/utils/QuotaCheckUtilsTest.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/utils/QuotaCheckUtilsTest.java @@ -17,6 +17,9 @@ package com.tencent.cloud.polaris.ratelimit.utils; +import java.util.HashMap; +import java.util.HashSet; + import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult; import com.tencent.polaris.ratelimit.api.core.LimitAPI; import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest; @@ -66,28 +69,59 @@ public class QuotaCheckUtilsTest { public void testGetQuota() { // Pass String serviceName = "TestApp1"; - QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null); + QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashMap<>(), null); + assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk); + assertThat(quotaResponse.getWaitMs()).isEqualTo(0); + assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk"); + + // Unirate waiting 1000ms + serviceName = "TestApp2"; + quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashMap<>(), null); + assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk); + assertThat(quotaResponse.getWaitMs()).isEqualTo(1000); + assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk"); + + // Rate limited + serviceName = "TestApp3"; + quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashMap<>(), null); + assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultLimited); + assertThat(quotaResponse.getWaitMs()).isEqualTo(0); + assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultLimited"); + + // Exception + serviceName = "TestApp4"; + quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashMap<>(), null); + assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk); + assertThat(quotaResponse.getWaitMs()).isEqualTo(0); + assertThat(quotaResponse.getInfo()).isEqualTo("get quota failed"); + } + + @Test + public void testGetQuota2() { + // Pass + String serviceName = "TestApp1"; + QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashSet<>(), null); assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk); assertThat(quotaResponse.getWaitMs()).isEqualTo(0); assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk"); // Unirate waiting 1000ms serviceName = "TestApp2"; - quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null); + quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashSet<>(), null); assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk); assertThat(quotaResponse.getWaitMs()).isEqualTo(1000); assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk"); // Rate limited serviceName = "TestApp3"; - quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null); + quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashSet<>(), null); assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultLimited); assertThat(quotaResponse.getWaitMs()).isEqualTo(0); assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultLimited"); // Exception serviceName = "TestApp4"; - quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null); + quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, new HashSet<>(), null); assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk); assertThat(quotaResponse.getWaitMs()).isEqualTo(0); assertThat(quotaResponse.getInfo()).isEqualTo("get quota failed"); diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/resources/ratelimit.json b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/resources/ratelimit.json new file mode 100644 index 000000000..92908114d --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/resources/ratelimit.json @@ -0,0 +1,95 @@ +{ + "id": "f7560cce829e4d4c8556a6be63539af5", + "service": "xxx", + "namespace": "default", + "subset": {}, + "priority": 0, + "resource": "QPS", + "type": "GLOBAL", + "labels": {}, + "amounts": [ + { + "maxAmount": 1, + "validDuration": "1s", + "precision": null, + "startAmount": null, + "minAmount": null + } + ], + "action": "REJECT", + "disable": false, + "report": null, + "ctime": "2022-12-11 21:56:59", + "mtime": "2023-03-10 15:40:33", + "revision": "6eec6f416bee40ecbf664c93add61358", + "service_token": null, + "adjuster": null, + "regex_combine": true, + "amountMode": "GLOBAL_TOTAL", + "failover": "FAILOVER_LOCAL", + "cluster": null, + "method": { + "type": "EXACT", + "value": "/xxx", + "value_type": "TEXT" + }, + "arguments": [ + { + "type": "CALLER_SERVICE", + "key": "default", + "value": { + "type": "EXACT", + "value": "xxx", + "value_type": "TEXT" + } + }, + { + "type": "HEADER", + "key": "xxx", + "value": { + "type": "EXACT", + "value": "xxx", + "value_type": "TEXT" + } + }, + { + "type": "QUERY", + "key": "yyy", + "value": { + "type": "EXACT", + "value": "yyy", + "value_type": "TEXT" + } + }, + { + "type": "METHOD", + "key": "$method", + "value": { + "type": "EXACT", + "value": "GET", + "value_type": "TEXT" + } + }, + { + "type": "CALLER_IP", + "key": "$caller_ip", + "value": { + "type": "EXACT", + "value": "127.0.0.1", + "value_type": "TEXT" + } + }, + { + "type": "CUSTOM", + "key": "xxx", + "value": { + "type": "EXACT", + "value": "xxx", + "value_type": "TEXT" + } + } + ], + "name": "xxx", + "etime": "2023-03-10 15:40:33", + "max_queue_delay": 30 +} \ No newline at end of file diff --git a/spring-cloud-starter-tencent-polaris-router/pom.xml b/spring-cloud-starter-tencent-polaris-router/pom.xml index 180edcc95..56ac0fe50 100644 --- a/spring-cloud-starter-tencent-polaris-router/pom.xml +++ b/spring-cloud-starter-tencent-polaris-router/pom.xml @@ -73,10 +73,10 @@ <artifactId>spring-retry</artifactId> <optional>true</optional> </dependency> - + <dependency> <groupId>org.springframework.cloud</groupId> - <artifactId>spring-cloud-netflix-zuul</artifactId> + <artifactId>spring-cloud-starter-netflix-zuul</artifactId> <optional>true</optional> </dependency> diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java index f06a8f323..26d5fbb88 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java @@ -136,24 +136,29 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule { // 2. filter by router List<Server> serversAfterRouter; - if (key instanceof PolarisRouterContext) { - // router implement for Feign and scg - PolarisRouterContext routerContext = (PolarisRouterContext) key; - serversAfterRouter = doRouter(allServers, routerContext); - } - else if (key instanceof HttpRequest) { - // router implement for rest template - HttpRequest request = (HttpRequest) key; - - String routerContextStr = request.getHeaders().getFirst(RouterConstant.HEADER_ROUTER_CONTEXT); - - if (StringUtils.isEmpty(routerContextStr)) { - serversAfterRouter = allServers; + if (key != null) { + if (key instanceof PolarisRouterContext) { + // router implement for Feign and scg + PolarisRouterContext routerContext = (PolarisRouterContext) key; + serversAfterRouter = doRouter(allServers, routerContext); + } + else if (key instanceof HttpRequest) { + // router implement for rest template + HttpRequest request = (HttpRequest) key; + + String routerContextStr = request.getHeaders().getFirst(RouterConstant.HEADER_ROUTER_CONTEXT); + + if (StringUtils.isEmpty(routerContextStr)) { + serversAfterRouter = allServers; + } + else { + PolarisRouterContext routerContext = JacksonUtils.deserialize(UriEncoder.decode(routerContextStr), + PolarisRouterContext.class); + serversAfterRouter = doRouter(allServers, routerContext); + } } else { - PolarisRouterContext routerContext = JacksonUtils.deserialize(UriEncoder.decode(routerContextStr), - PolarisRouterContext.class); - serversAfterRouter = doRouter(allServers, routerContext); + serversAfterRouter = allServers; } } else { diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/ContextConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/ContextConstant.java index 66a8f08b5..4c3307a63 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/ContextConstant.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/ContextConstant.java @@ -79,4 +79,29 @@ public final class ContextConstant { */ public static Integer STAT_REPORTER_ORDER = 1; } + + public static final class Zuul { + + /** + * polaris circuit breaker. + */ + public static final String POLARIS_CIRCUIT_BREAKER = "PolarisCircuitBreaker"; + + /** + * timestamp before route. + */ + public static final String POLARIS_PRE_ROUTE_TIME = "PolarisPreRouteTime"; + /** + * is in routing state. + */ + public static final String POLARIS_IS_IN_ROUTING_STATE = "PolarisIsInRoutingState"; + + /** + * polaris circuit breaker. + */ + public static final String ENHANCED_PLUGIN_CONTEXT = "EnhancedPluginContext"; + + private Zuul() { + } + } } 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/constant/MetadataConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java index e51d7042a..d007a8921 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java @@ -26,17 +26,10 @@ import org.springframework.core.Ordered; */ public final class MetadataConstant { - /** - * Default Private Constructor. - */ - private MetadataConstant() { - } - /** * sct transitive header prefix. */ public static final String SCT_TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-"; - /** * sct transitive header prefix length. */ @@ -46,12 +39,15 @@ public final class MetadataConstant { * polaris transitive header prefix. */ public static final String POLARIS_TRANSITIVE_HEADER_PREFIX = "X-Polaris-Metadata-Transitive-"; - /** * polaris transitive header prefix length. */ public static final int POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH = POLARIS_TRANSITIVE_HEADER_PREFIX.length(); + private MetadataConstant() { + + } + /** * Order of filter, interceptor, ... */ @@ -60,7 +56,7 @@ public final class MetadataConstant { /** * Order of filter. */ - public static final int WEB_FILTER_ORDER = Ordered.HIGHEST_PRECEDENCE + 13; + public static final int WEB_FILTER_ORDER = Ordered.HIGHEST_PRECEDENCE + 9; /** * Order of MetadataFirstFeignInterceptor. @@ -98,4 +94,19 @@ public final class MetadataConstant { */ public static final String METADATA_CONTEXT = "SCT-METADATA-CONTEXT"; } + + public static class DefaultMetadata { + + /** + * Default Metadata Source Service Namespace Key. + */ + public static final String DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE = "source_service_namespace"; + + /** + * Default Metadata Source Service Name Key. + */ + public static final String DEFAULT_METADATA_SOURCE_SERVICE_NAME = "source_service_name"; + + } + } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContextHolder.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContextHolder.java index 60983ef69..90a949d2e 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContextHolder.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContextHolder.java @@ -29,8 +29,6 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import static com.tencent.cloud.common.metadata.MetadataContext.FRAGMENT_DISPOSABLE; -import static com.tencent.cloud.common.metadata.MetadataContext.FRAGMENT_RAW_TRANSHEADERS; -import static com.tencent.cloud.common.metadata.MetadataContext.FRAGMENT_TRANSITIVE; import static com.tencent.cloud.common.metadata.MetadataContext.FRAGMENT_UPSTREAM_DISPOSABLE; /** @@ -67,11 +65,11 @@ public final class MetadataContextHolder { // init static transitive metadata MetadataContext metadataContext = new MetadataContext(); - metadataContext.putFragmentContext(FRAGMENT_TRANSITIVE, staticMetadataManager.getMergedStaticTransitiveMetadata()); - metadataContext.putFragmentContext(FRAGMENT_DISPOSABLE, staticMetadataManager.getMergedStaticDisposableMetadata()); + metadataContext.setTransitiveMetadata(staticMetadataManager.getMergedStaticTransitiveMetadata()); + metadataContext.setDisposableMetadata(staticMetadataManager.getMergedStaticDisposableMetadata()); if (StringUtils.hasText(staticMetadataManager.getTransHeaderFromEnv())) { - metadataContext.putContext(FRAGMENT_RAW_TRANSHEADERS, staticMetadataManager.getTransHeaderFromEnv(), ""); + metadataContext.setTransHeaders(staticMetadataManager.getTransHeaderFromEnv(), ""); } METADATA_CONTEXT.set(metadataContext); @@ -132,18 +130,19 @@ public final class MetadataContextHolder { // Save transitive metadata to ThreadLocal. if (!CollectionUtils.isEmpty(dynamicTransitiveMetadata)) { - Map<String, String> staticTransitiveMetadata = metadataContext.getFragmentContext(FRAGMENT_TRANSITIVE); + Map<String, String> staticTransitiveMetadata = metadataContext.getTransitiveMetadata(); Map<String, String> mergedTransitiveMetadata = new HashMap<>(); mergedTransitiveMetadata.putAll(staticTransitiveMetadata); mergedTransitiveMetadata.putAll(dynamicTransitiveMetadata); - metadataContext.putFragmentContext(FRAGMENT_TRANSITIVE, Collections.unmodifiableMap(mergedTransitiveMetadata)); - - Map<String, String> mergedDisposableMetadata = new HashMap<>(dynamicDisposableMetadata); - metadataContext.putFragmentContext(FRAGMENT_UPSTREAM_DISPOSABLE, Collections.unmodifiableMap(mergedDisposableMetadata)); - - Map<String, String> staticDisposableMetadata = metadataContext.getFragmentContext(FRAGMENT_DISPOSABLE); - metadataContext.putFragmentContext(FRAGMENT_DISPOSABLE, Collections.unmodifiableMap(staticDisposableMetadata)); + metadataContext.setTransitiveMetadata(Collections.unmodifiableMap(mergedTransitiveMetadata)); } + if (!CollectionUtils.isEmpty(dynamicDisposableMetadata)) { + Map<String, String> mergedUpstreamDisposableMetadata = new HashMap<>(dynamicDisposableMetadata); + metadataContext.setUpstreamDisposableMetadata(Collections.unmodifiableMap(mergedUpstreamDisposableMetadata)); + } + + Map<String, String> staticDisposableMetadata = metadataContext.getFragmentContext(FRAGMENT_DISPOSABLE); + metadataContext.setDisposableMetadata(Collections.unmodifiableMap(staticDisposableMetadata)); MetadataContextHolder.set(metadataContext); } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/StaticMetadataManager.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/StaticMetadataManager.java index 2cf156713..d37deb006 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/StaticMetadataManager.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/StaticMetadataManager.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.spi.InstanceMetadataProvider; @@ -34,7 +35,7 @@ import org.springframework.util.CollectionUtils; /** * manage metadata from env/config file/custom spi. * - * @author lepdou 2022-05-20 + * @author lepdou, Haotian Zhang */ public class StaticMetadataManager { /** @@ -53,7 +54,6 @@ public class StaticMetadataManager { private static final String ENV_METADATA_PREFIX = "SCT_METADATA_CONTENT_"; private static final int ENV_METADATA_PREFIX_LENGTH = ENV_METADATA_PREFIX.length(); private static final String ENV_METADATA_CONTENT_TRANSITIVE = "SCT_METADATA_CONTENT_TRANSITIVE"; - private static final String ENV_METADATA_CONTENT_DISPOSABLE = "SCT_METADATA_CONTENT_DISPOSABLE"; /** * This is the key of the header's key list needed to be transmitted. The list is a string split with ,. @@ -82,14 +82,14 @@ public class StaticMetadataManager { private String campus; public StaticMetadataManager(MetadataLocalProperties metadataLocalProperties, - InstanceMetadataProvider instanceMetadataProvider) { + List<InstanceMetadataProvider> instanceMetadataProviders) { parseConfigMetadata(metadataLocalProperties); parseEnvMetadata(); - parseCustomMetadata(instanceMetadataProvider); + parseCustomMetadata(instanceMetadataProviders); - parseLocationMetadata(metadataLocalProperties, instanceMetadataProvider); + parseLocationMetadata(metadataLocalProperties, instanceMetadataProviders); merge(); @@ -183,21 +183,25 @@ public class StaticMetadataManager { } @SuppressWarnings("DuplicatedCode") - private void parseCustomMetadata(InstanceMetadataProvider instanceMetadataProvider) { - if (instanceMetadataProvider == null) { - customSPIMetadata = Collections.emptyMap(); - customSPITransitiveMetadata = Collections.emptyMap(); - customSPIDisposableMetadata = Collections.emptyMap(); - return; + private void parseCustomMetadata(List<InstanceMetadataProvider> instanceMetadataProviders) { + // init customSPIMetadata + customSPIMetadata = new HashMap<>(); + customSPITransitiveMetadata = new HashMap<>(); + customSPIDisposableMetadata = new HashMap<>(); + if (!CollectionUtils.isEmpty(instanceMetadataProviders)) { + instanceMetadataProviders.forEach(this::parseCustomMetadata); } + customSPIMetadata = Collections.unmodifiableMap(customSPIMetadata); + customSPITransitiveMetadata = Collections.unmodifiableMap(customSPITransitiveMetadata); + customSPIDisposableMetadata = Collections.unmodifiableMap(customSPIDisposableMetadata); + } + @SuppressWarnings("DuplicatedCode") + private void parseCustomMetadata(InstanceMetadataProvider instanceMetadataProvider) { // resolve all metadata Map<String, String> allMetadata = instanceMetadataProvider.getMetadata(); - if (allMetadata == null) { - customSPIMetadata = Collections.emptyMap(); - } - else { - customSPIMetadata = Collections.unmodifiableMap(allMetadata); + if (!CollectionUtils.isEmpty(allMetadata)) { + customSPIMetadata.putAll(allMetadata); } // resolve transitive metadata @@ -210,7 +214,7 @@ public class StaticMetadataManager { } } } - customSPITransitiveMetadata = Collections.unmodifiableMap(transitiveMetadata); + customSPITransitiveMetadata.putAll(transitiveMetadata); Set<String> disposableKeys = instanceMetadataProvider.getDisposableMetadataKeys(); Map<String, String> disposableMetadata = new HashMap<>(); @@ -221,7 +225,7 @@ public class StaticMetadataManager { } } } - customSPIDisposableMetadata = Collections.unmodifiableMap(disposableMetadata); + customSPIDisposableMetadata.putAll(disposableMetadata); } private void merge() { @@ -231,6 +235,7 @@ public class StaticMetadataManager { mergedMetadataResult.putAll(configMetadata); mergedMetadataResult.putAll(envMetadata); mergedMetadataResult.putAll(customSPIMetadata); + this.mergedStaticMetadata = Collections.unmodifiableMap(mergedMetadataResult); Map<String, String> mergedTransitiveMetadataResult = new HashMap<>(); @@ -247,10 +252,16 @@ public class StaticMetadataManager { } private void parseLocationMetadata(MetadataLocalProperties metadataLocalProperties, - InstanceMetadataProvider instanceMetadataProvider) { + List<InstanceMetadataProvider> instanceMetadataProviders) { // resolve region info - if (instanceMetadataProvider != null) { - region = instanceMetadataProvider.getRegion(); + if (!CollectionUtils.isEmpty(instanceMetadataProviders)) { + Set<String> providerRegions = instanceMetadataProviders.stream().map(InstanceMetadataProvider::getRegion).filter(region -> !StringUtils.isBlank(region)).collect(Collectors.toSet()); + if (!CollectionUtils.isEmpty(providerRegions)) { + if (providerRegions.size() > 1) { + throw new IllegalArgumentException("Multiple Regions Provided in InstanceMetadataProviders"); + } + region = providerRegions.iterator().next(); + } } if (StringUtils.isBlank(region)) { region = System.getenv(ENV_METADATA_REGION); @@ -260,8 +271,14 @@ public class StaticMetadataManager { } // resolve zone info - if (instanceMetadataProvider != null) { - zone = instanceMetadataProvider.getZone(); + if (!CollectionUtils.isEmpty(instanceMetadataProviders)) { + Set<String> providerZones = instanceMetadataProviders.stream().map(InstanceMetadataProvider::getZone).filter(zone -> !StringUtils.isBlank(zone)).collect(Collectors.toSet()); + if (!CollectionUtils.isEmpty(providerZones)) { + if (providerZones.size() > 1) { + throw new IllegalArgumentException("Multiple Zones Provided in InstanceMetadataProviders"); + } + zone = providerZones.iterator().next(); + } } if (StringUtils.isBlank(zone)) { zone = System.getenv(ENV_METADATA_ZONE); @@ -271,8 +288,14 @@ public class StaticMetadataManager { } // resolve campus info - if (instanceMetadataProvider != null) { - campus = instanceMetadataProvider.getCampus(); + if (!CollectionUtils.isEmpty(instanceMetadataProviders)) { + Set<String> providerCampus = instanceMetadataProviders.stream().map(InstanceMetadataProvider::getCampus).filter(campus -> !StringUtils.isBlank(campus)).collect(Collectors.toSet()); + if (!CollectionUtils.isEmpty(providerCampus)) { + if (providerCampus.size() > 1) { + throw new IllegalArgumentException("Multiple Campus Provided in InstanceMetadataProviders"); + } + campus = providerCampus.iterator().next(); + } } if (StringUtils.isBlank(campus)) { campus = System.getenv(ENV_METADATA_CAMPUS); diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfiguration.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfiguration.java index 060bed30d..a5a02b67b 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfiguration.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfiguration.java @@ -18,8 +18,12 @@ package com.tencent.cloud.common.metadata.config; +import java.util.List; + import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.common.spi.InstanceMetadataProvider; +import com.tencent.cloud.common.spi.impl.DefaultInstanceMetadataProvider; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -42,10 +46,15 @@ public class MetadataAutoConfiguration { return new MetadataLocalProperties(); } + @Bean + public InstanceMetadataProvider defaultInstanceMetadataProvider(ApplicationContextAwareUtils applicationContextAwareUtils) { + return new DefaultInstanceMetadataProvider(applicationContextAwareUtils); + } + @Bean public StaticMetadataManager metadataManager(MetadataLocalProperties metadataLocalProperties, - @Nullable InstanceMetadataProvider instanceMetadataProvider) { - return new StaticMetadataManager(metadataLocalProperties, instanceMetadataProvider); + @Nullable List<InstanceMetadataProvider> instanceMetadataProviders) { + return new StaticMetadataManager(metadataLocalProperties, instanceMetadataProviders); } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/spi/impl/DefaultInstanceMetadataProvider.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/spi/impl/DefaultInstanceMetadataProvider.java new file mode 100644 index 000000000..e4a99636c --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/spi/impl/DefaultInstanceMetadataProvider.java @@ -0,0 +1,63 @@ +/* + * 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.spi.impl; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.tencent.cloud.common.spi.InstanceMetadataProvider; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; + +import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAME; +import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE; +import static com.tencent.cloud.common.metadata.MetadataContext.LOCAL_NAMESPACE; +import static com.tencent.cloud.common.metadata.MetadataContext.LOCAL_SERVICE; + +/** + * DefaultInstanceMetadataProvider. + * provide DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE, DEFAULT_METADATA_SOURCE_SERVICE_NAME + * + * @author sean yu + */ +public class DefaultInstanceMetadataProvider implements InstanceMetadataProvider { + + private final ApplicationContextAwareUtils applicationContextAwareUtils; + + // ensure ApplicationContextAwareUtils init before + public DefaultInstanceMetadataProvider(ApplicationContextAwareUtils applicationContextAwareUtils) { + this.applicationContextAwareUtils = applicationContextAwareUtils; + } + + @Override + public Map<String, String> getMetadata() { + return new HashMap<String, String>() {{ + put(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE, LOCAL_NAMESPACE); + put(DEFAULT_METADATA_SOURCE_SERVICE_NAME, LOCAL_SERVICE); + }}; + } + + @Override + public Set<String> getDisposableMetadataKeys() { + return new HashSet<>(Arrays.asList(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE, DEFAULT_METADATA_SOURCE_SERVICE_NAME)); + } + +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java index 2951d7848..a6c14a0d6 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java @@ -111,14 +111,4 @@ public final class JacksonUtils { throw new RuntimeException("Json to map failed.", e); } } - - public static <T> T json2JavaBean(String content, Class<T> valueType) { - try { - return OM.readValue(content, valueType); - } - catch (Exception e) { - LOG.error("json {} to class {} failed. ", content, valueType, e); - throw new RuntimeException("json to class failed.", e); - } - } } 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-commons/src/main/java/com/tencent/cloud/common/util/ZuulFilterUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ZuulFilterUtils.java new file mode 100644 index 000000000..a0bae9e3f --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ZuulFilterUtils.java @@ -0,0 +1,80 @@ +/* + * 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; + +import java.net.URL; + +import javax.servlet.http.HttpServletRequest; + +import com.netflix.zuul.context.RequestContext; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.web.util.UriUtils; +import org.springframework.web.util.WebUtils; + +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.REQUEST_URI_KEY; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY; + +/** + * Utils for Zuul filter. + * + * @author Haotian Zhang + */ +public final class ZuulFilterUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(ZuulFilterUtils.class); + + private ZuulFilterUtils() { + } + + public static String getServiceId(RequestContext context) { + String serviceId = (String) context.get(SERVICE_ID_KEY); + if (StringUtils.isBlank(serviceId)) { + URL url = context.getRouteHost(); + if (url != null) { + serviceId = url.getAuthority(); + context.set(SERVICE_ID_KEY, serviceId); + } + } + return serviceId; + } + + public static String getPath(RequestContext context) { + HttpServletRequest request = context.getRequest(); + String uri = request.getRequestURI(); + String contextURI = (String) context.get(REQUEST_URI_KEY); + if (contextURI != null) { + try { + uri = UriUtils.encodePath(contextURI, characterEncoding(request)); + } + catch (Exception e) { + LOGGER.debug("unable to encode uri path from context, falling back to uri from request", e); + } + } + // remove double slashes + uri = uri.replace("//", "/"); + return uri; + } + + private static String characterEncoding(HttpServletRequest request) { + return request.getCharacterEncoding() != null ? request.getCharacterEncoding() + : WebUtils.DEFAULT_CHARACTER_ENCODING; + } +} diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/StaticMetadataManagerTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/StaticMetadataManagerTest.java index 2283e4bbe..784213c57 100644 --- a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/StaticMetadataManagerTest.java +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/StaticMetadataManagerTest.java @@ -18,6 +18,7 @@ package com.tencent.cloud.common.metadata; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -26,20 +27,33 @@ import java.util.Set; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.spi.InstanceMetadataProvider; +import com.tencent.cloud.common.spi.impl.DefaultInstanceMetadataProvider; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +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.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; import uk.org.webcompere.systemstubs.jupiter.SystemStub; import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; +import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAME; +import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE; +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.anyString; import static org.mockito.Mockito.when; /** * test for {@link StaticMetadataManager}. - *@author lepdou 2022-06-27 + * + * @author lepdou 2022-06-27 */ @ExtendWith({MockitoExtension.class, SystemStubsExtension.class}) public class StaticMetadataManagerTest { @@ -52,6 +66,26 @@ public class StaticMetadataManagerTest { @Mock private MetadataLocalProperties metadataLocalProperties; + 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 testParseConfigMetadata() { Map<String, String> content = new HashMap<>(); @@ -62,6 +96,7 @@ public class StaticMetadataManagerTest { when(metadataLocalProperties.getContent()).thenReturn(content); when(metadataLocalProperties.getTransitive()).thenReturn(Collections.singletonList("k1")); + when(metadataLocalProperties.getDisposable()).thenReturn(Collections.singletonList("k1")); StaticMetadataManager metadataManager = new StaticMetadataManager(metadataLocalProperties, null); @@ -74,6 +109,10 @@ public class StaticMetadataManagerTest { assertThat(transitiveMetadata.size()).isEqualTo(1); assertThat(transitiveMetadata.get("k1")).isEqualTo("v1"); + Map<String, String> disposableMetadata = metadataManager.getConfigDisposableMetadata(); + assertThat(disposableMetadata.size()).isEqualTo(1); + assertThat(disposableMetadata.get("k1")).isEqualTo("v1"); + assertThat(metadataManager.getZone()).isEqualTo("zone1"); assertThat(metadataManager.getRegion()).isEqualTo("region1"); @@ -92,17 +131,25 @@ public class StaticMetadataManagerTest { when(metadataLocalProperties.getTransitive()).thenReturn(Collections.singletonList("k1")); StaticMetadataManager metadataManager = new StaticMetadataManager(metadataLocalProperties, - new MockedMetadataProvider()); + Arrays.asList(new MockedMetadataProvider(), new DefaultInstanceMetadataProvider(null))); Map<String, String> metadata = metadataManager.getAllCustomMetadata(); - assertThat(metadata.size()).isEqualTo(3); + assertThat(metadata.size()).isEqualTo(5); assertThat(metadata.get("k1")).isEqualTo("v1"); assertThat(metadata.get("k2")).isEqualTo("v22"); assertThat(metadata.get("k3")).isEqualTo("v33"); + assertThat(metadata.get(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE)).isEqualTo(NAMESPACE_TEST); + assertThat(metadata.get(DEFAULT_METADATA_SOURCE_SERVICE_NAME)).isEqualTo(SERVICE_PROVIDER); Map<String, String> transitiveMetadata = metadataManager.getCustomSPITransitiveMetadata(); assertThat(transitiveMetadata.size()).isEqualTo(1); - assertThat(metadata.get("k2")).isEqualTo("v22"); + assertThat(transitiveMetadata.get("k2")).isEqualTo("v22"); + + Map<String, String> disposableMetadata = metadataManager.getCustomSPIDisposableMetadata(); + assertThat(disposableMetadata.size()).isEqualTo(3); + assertThat(disposableMetadata.get("k3")).isEqualTo("v33"); + assertThat(disposableMetadata.get(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE)).isEqualTo(NAMESPACE_TEST); + assertThat(disposableMetadata.get(DEFAULT_METADATA_SOURCE_SERVICE_NAME)).isEqualTo(SERVICE_PROVIDER); assertThat(metadataManager.getZone()).isEqualTo("zone2"); assertThat(metadataManager.getRegion()).isEqualTo("region1"); @@ -125,29 +172,38 @@ public class StaticMetadataManagerTest { when(metadataLocalProperties.getTransitive()).thenReturn(Collections.singletonList("k1")); StaticMetadataManager metadataManager = new StaticMetadataManager(metadataLocalProperties, - new MockedMetadataProvider()); + Arrays.asList(new MockedMetadataProvider(), new DefaultInstanceMetadataProvider(null))); Map<String, String> metadata = metadataManager.getMergedStaticMetadata(); - assertThat(metadata.size()).isEqualTo(6); + assertThat(metadata.size()).isEqualTo(8); assertThat(metadata.get("k1")).isEqualTo("v1"); assertThat(metadata.get("k2")).isEqualTo("v22"); assertThat(metadata.get("k3")).isEqualTo("v33"); + assertThat(metadata.get(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE)).isEqualTo(NAMESPACE_TEST); + assertThat(metadata.get(DEFAULT_METADATA_SOURCE_SERVICE_NAME)).isEqualTo(SERVICE_PROVIDER); Map<String, String> transitiveMetadata = metadataManager.getMergedStaticTransitiveMetadata(); assertThat(transitiveMetadata.size()).isEqualTo(2); - assertThat(metadata.get("k1")).isEqualTo("v1"); - assertThat(metadata.get("k2")).isEqualTo("v22"); + assertThat(transitiveMetadata.get("k1")).isEqualTo("v1"); + assertThat(transitiveMetadata.get("k2")).isEqualTo("v22"); + + Map<String, String> disposableMetadata = metadataManager.getMergedStaticDisposableMetadata(); + assertThat(disposableMetadata.size()).isEqualTo(3); + assertThat(disposableMetadata.get("k3")).isEqualTo("v33"); + assertThat(disposableMetadata.get(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE)).isEqualTo(NAMESPACE_TEST); + assertThat(disposableMetadata.get(DEFAULT_METADATA_SOURCE_SERVICE_NAME)).isEqualTo(SERVICE_PROVIDER); assertThat(metadataManager.getAllEnvMetadata()).isEmpty(); assertThat(metadataManager.getEnvTransitiveMetadata()).isEmpty(); assertThat(metadataManager.getZone()).isEqualTo("zone2"); assertThat(metadataManager.getRegion()).isEqualTo("region1"); + assertThat(metadataManager.getCampus()).isEqualTo("campus2"); Map<String, String> locationInfo = metadataManager.getLocationMetadata(); assertThat(locationInfo.get("zone")).isEqualTo("zone2"); assertThat(locationInfo.get("region")).isEqualTo("region1"); - assertThat(locationInfo.get("campus")).isEqualTo("campus1"); + assertThat(locationInfo.get("campus")).isEqualTo("campus2"); } @Test @@ -194,6 +250,13 @@ public class StaticMetadataManagerTest { return transitiveKeys; } + @Override + public Set<String> getDisposableMetadataKeys() { + Set<String> transitiveKeys = new HashSet<>(); + transitiveKeys.add("k3"); + return transitiveKeys; + } + @Override public String getRegion() { return "region1"; @@ -206,7 +269,7 @@ public class StaticMetadataManagerTest { @Override public String getCampus() { - return null; + return "campus2"; } } } diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfigurationTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfigurationTest.java index 1987843e5..c5cda081c 100644 --- a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfigurationTest.java +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfigurationTest.java @@ -18,6 +18,8 @@ package com.tencent.cloud.common.metadata.config; + +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -33,12 +35,17 @@ import org.springframework.boot.test.context.runner.WebApplicationContextRunner; */ public class MetadataAutoConfigurationTest { - private final ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner(); + private final ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner().withPropertyValues( + "spring.application.name=test" + ); - private final WebApplicationContextRunner webApplicationContextRunner = new WebApplicationContextRunner(); + private final WebApplicationContextRunner webApplicationContextRunner = new WebApplicationContextRunner().withPropertyValues( + "spring.application.name=test" + ); - private final ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = - new ReactiveWebApplicationContextRunner(); + private final ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = new ReactiveWebApplicationContextRunner().withPropertyValues( + "spring.application.name=test" + ); /** * No any web application. @@ -46,7 +53,7 @@ public class MetadataAutoConfigurationTest { @Test public void test1() { this.applicationContextRunner - .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class, ApplicationContextAwareUtils.class)) .run(context -> { Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); }); @@ -58,7 +65,7 @@ public class MetadataAutoConfigurationTest { @Test public void test2() { this.webApplicationContextRunner - .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class, ApplicationContextAwareUtils.class)) .run(context -> { Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); }); @@ -70,7 +77,7 @@ public class MetadataAutoConfigurationTest { @Test public void test3() { this.reactiveWebApplicationContextRunner - .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class, ApplicationContextAwareUtils.class)) .run(context -> { Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); }); diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/pojo/PolarisServiceInstanceTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/pojo/PolarisServiceInstanceTest.java index afa2eec25..107310331 100644 --- a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/pojo/PolarisServiceInstanceTest.java +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/pojo/PolarisServiceInstanceTest.java @@ -17,6 +17,7 @@ package com.tencent.cloud.common.pojo; +import com.tencent.polaris.api.pojo.DefaultInstance; import com.tencent.polaris.api.pojo.Instance; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -38,7 +39,7 @@ public class PolarisServiceInstanceTest { @Test @DisplayName("test getters and setters.") - void test() { + public void test1() { Instance secureInstance = mock(Instance.class); doReturn("test-ID").when(secureInstance).getId(); doReturn(SERVICE_PROVIDER).when(secureInstance).getService(); @@ -53,6 +54,7 @@ public class PolarisServiceInstanceTest { assertThat(securePolarisServiceInstance.getPort()).isEqualTo(8080); assertThat(securePolarisServiceInstance.isSecure()).isTrue(); assertThat(securePolarisServiceInstance.getScheme()).isEqualTo("https"); + assertThat(securePolarisServiceInstance.getUri().toString()).isEqualTo("https://1.1.1.1:8080"); Instance insecureInstance = mock(Instance.class); doReturn("http").when(insecureInstance).getProtocol(); @@ -60,4 +62,37 @@ public class PolarisServiceInstanceTest { assertThat(insecurePolarisServiceInstance.isSecure()).isFalse(); assertThat(insecurePolarisServiceInstance.getScheme()).isEqualTo("http"); } + + + @Test + @DisplayName("test equals().") + public void test2() { + DefaultInstance instance1 = new DefaultInstance(); + instance1.setId("test-1"); + instance1.setProtocol("http"); + PolarisServiceInstance polarisServiceInstance1 = new PolarisServiceInstance(instance1); + + DefaultInstance instance2 = new DefaultInstance(); + instance2.setId("test-1"); + instance2.setProtocol("http"); + PolarisServiceInstance polarisServiceInstance2 = new PolarisServiceInstance(instance2); + + assertThat(polarisServiceInstance1.equals(polarisServiceInstance2)).isTrue(); + } + + @Test + @DisplayName("test hashCode().") + public void test3() { + DefaultInstance instance1 = new DefaultInstance(); + instance1.setId("test-1"); + instance1.setProtocol("http"); + PolarisServiceInstance polarisServiceInstance1 = new PolarisServiceInstance(instance1); + + DefaultInstance instance2 = new DefaultInstance(); + instance2.setId("test-1"); + instance2.setProtocol("http"); + PolarisServiceInstance polarisServiceInstance2 = new PolarisServiceInstance(instance2); + + assertThat(polarisServiceInstance1.hashCode()).isEqualTo(polarisServiceInstance2.hashCode()); + } } diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/BeanFactoryUtilsTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/BeanFactoryUtilsTest.java index 9a94a32e4..e4e6001f9 100644 --- a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/BeanFactoryUtilsTest.java +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/BeanFactoryUtilsTest.java @@ -19,10 +19,16 @@ package com.tencent.cloud.common.util; import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.ResolvableType; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * Test for {@link BeanFactoryUtils}. @@ -41,6 +47,11 @@ public class BeanFactoryUtilsTest { assertThat(childBeanFactory.getBeansOfType(Foo.class)).isEmpty(); assertThat(BeanFactoryUtils.getBeans(childBeanFactory, Foo.class).size()).isEqualTo(1); assertThat(BeanFactoryUtils.getBeans(childBeanFactory, Bar.class)).isEmpty(); + + MockBeanFactory mockBeanFactory = new MockBeanFactory(); + assertThatThrownBy(() -> BeanFactoryUtils.getBeans(mockBeanFactory, Bar.class)) + .isExactlyInstanceOf(RuntimeException.class) + .hasMessageContaining("bean factory not support get list bean."); } static class Foo { @@ -50,4 +61,82 @@ public class BeanFactoryUtilsTest { static class Bar { } + + static class MockBeanFactory implements BeanFactory { + + @Override + public Object getBean(String s) throws BeansException { + return null; + } + + @Override + public <T> T getBean(String s, Class<T> aClass) throws BeansException { + return null; + } + + @Override + public Object getBean(String s, Object... objects) throws BeansException { + return null; + } + + @Override + public <T> T getBean(Class<T> aClass) throws BeansException { + return null; + } + + @Override + public <T> T getBean(Class<T> aClass, Object... objects) throws BeansException { + return null; + } + + @Override + public <T> ObjectProvider<T> getBeanProvider(Class<T> aClass) { + return null; + } + + @Override + public <T> ObjectProvider<T> getBeanProvider(ResolvableType resolvableType) { + return null; + } + + @Override + public boolean containsBean(String s) { + return false; + } + + @Override + public boolean isSingleton(String s) throws NoSuchBeanDefinitionException { + return false; + } + + @Override + public boolean isPrototype(String s) throws NoSuchBeanDefinitionException { + return false; + } + + @Override + public boolean isTypeMatch(String s, ResolvableType resolvableType) throws NoSuchBeanDefinitionException { + return false; + } + + @Override + public boolean isTypeMatch(String s, Class<?> aClass) throws NoSuchBeanDefinitionException { + return false; + } + + @Override + public Class<?> getType(String s) throws NoSuchBeanDefinitionException { + return null; + } + + @Override + public Class<?> getType(String s, boolean b) throws NoSuchBeanDefinitionException { + return null; + } + + @Override + public String[] getAliases(String s) { + return new String[0]; + } + } } diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/JacksonUtilsTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/JacksonUtilsTest.java index 962f014a3..df2b3aa7b 100644 --- a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/JacksonUtilsTest.java +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/JacksonUtilsTest.java @@ -25,6 +25,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.util.StringUtils; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -43,6 +45,7 @@ public class JacksonUtilsTest { sourceMap.put("k2", "v2"); sourceMap.put("k3", "v3"); assertThat(JacksonUtils.serialize2Json(sourceMap)).isEqualTo("{\"k1\":\"v1\",\"k2\":\"v2\",\"k3\":\"v3\"}"); + assertThat(StringUtils.trimAllWhitespace(JacksonUtils.serialize2Json(sourceMap, true))).isEqualTo("{\"k1\":\"v1\",\"k2\":\"v2\",\"k3\":\"v3\"}"); } @Test diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index 15fe089d4..09f414a21 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -70,10 +70,10 @@ </developers> <properties> - <revision>1.11.1-Hoxton.SR12-SNAPSHOT</revision> + <revision>1.11.2-Hoxton.SR12-SNAPSHOT</revision> <!-- Dependencies --> - <polaris.version>1.12.0</polaris.version> + <polaris.version>1.12.2</polaris.version> <guava.version>31.0.1-jre</guava.version> <logback.version>1.2.11</logback.version> <mocktio.version>4.5.1</mocktio.version> diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README-zh.md b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README-zh.md index 23fe50ad6..30679375a 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README-zh.md +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README-zh.md @@ -1,27 +1,24 @@ -# Spring Cloud Polaris CircuitBreaker Example +# Spring Cloud Polaris Circuitbreaker example ## 样例简介 本样例将介绍如何在Spring Cloud项目中使用```spring-cloud-starter-tencent-polaris-circuitbreaker```以使用其各项功能。 -该样例分为两个微服务,即 - -1. ```polaris-circuitbreaker-example-a``` -2. ```polaris-circuitbreaker-example-b``` 有两个实例 B(默认正常服务)和 B2(模拟异常服务) - -``` polaris-circuitbreaker-example-a``` 对 ```polaris-circuitbreaker-example-b```发生调用。 +本样例包括被调方```polaris-circuitbreaker-callee-service```、```polaris-circuitbreaker-callee-service2```和主调方```polaris-circuitbreaker-feign-example```、```polaris-circuitbreaker-gateway-example```、```polaris-circuitbreaker-webclient-example```。 ## 使用说明 ### 修改配置 -修改 resource/bootstrap.yml 中北极星的服务端地址 +配置如下所示。其中,${ip}和${port}为Polaris后端服务的IP地址与端口号。 ```yaml spring: + application: + name: ${application.name} cloud: polaris: - address: grpc://${ip}:8091 + address: ${ip}:${port} ``` ### 启动样例 @@ -30,43 +27,40 @@ spring: 参考[Polaris Getting Started](https://github.com/PolarisMesh/polaris#getting-started)。 -### 启动应用 +#### 启动被调应用 -- IDEA启动 +分别启动```polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service```、```polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2``` -分别启动 -1. ```spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a```下的```ServiceA``` -2. ```spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b```下的```ServiceB``` -3. ```spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2```下的```ServiceB2``` +#### 启动主调应用 +##### 启动Feign并验证 -## 验证 +启动```polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example```。 -### Feign调用 +发送请求`curl --location --request GET 'http://127.0.0.1:48080/example/service/a/getBServiceInfo/fallbackFromPolaris'`, 验证熔断和Polaris-server远程拉取降级。 -执行以下命令发起Feign调用,其逻辑为```ServiceB```抛出一个异常 +发送请求`curl --location --request GET 'http://127.0.0.1:48080/example/service/a/getBServiceInfo/fallbackFromCode'`, 验证熔断和代码降级。 -```shell -curl -L -X GET 'localhost:48080/example/service/a/getBServiceInfo' -``` +##### 启动RestTemplate并验证 -预期返回情况: +启动```polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example```。 -在出现 -``` -hello world ! I'm a service B1 -``` +发送请求`curl --location --request GET 'http://127.0.0.1:48080/example/service/a/getBServiceInfo/fallbackFromPolaris'`, 验证熔断和Polaris-server远程拉取降级。 + +发送请求`curl --location --request GET 'http://127.0.0.1:48080/example/service/a/getBServiceInfo/fallbackFromCode'`, 验证熔断和代码降级。 + +##### 启动WebClient并验证 + +启动```polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example```。 + +发送请求`curl --location --request GET 'http://127.0.0.1:48080/example/service/a/getBServiceInfo'`, 验证熔断和代码降级。 -时,表示 B2 已经被熔断了,请求只会打到 B1。 +##### 启动SCG并验证 -### 验证更多场景 +启动```polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example```。 -您也可以调整 ```example-b``` 和 ```example-b2``` 中 ```resource/bootstrap.yml``` is-throw-runtime-exception -参数调整服务是否抛出异常。 +发送请求`curl --location --request GET 'http://127.0.0.1:48080/polaris-circuitbreaker-callee-service/example/service/b/info'`, 验证熔断和代码降级。 -例如测试以下场景: -1. 两个实例都是正常的,这时候预期是 B1 和 B2 都能正常被调用到 -2. 一个实例正常一个实例不正常,这时候预期是不正常实例被熔断,请求只会打到正常的实例 -3. 两个实例都不正常,这时候 Feign 会自动 Fallback 到 ProviderBFallback.java 的实现类 +修改```polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/resources/bootstrap.yml```。删除本地fallback方法并重启,验证熔断和Polaris-server远程拉取降级。 diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md index d298346d1..0c297ef02 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md @@ -1,10 +1,10 @@ -# Spring Cloud Polaris CircuitBreaker Example +# Spring Cloud Polaris Circuitbreaker example ## Example Introduction -This example shows how to use```spring-cloud-starter-tencent-polaris-circuitbreaker``` in Spring Cloud project and other features +This example shows how to use```spring-cloud-starter-tencent-polaris-circuitbreaker```in Spring Cloud project for its features. -This example is divided to two microservice, ```polaris-circuitbreaker-example-a``` and ```polaris-circuitbreaker-example-b```. In these two microservices, ```polaris-circuitbreaker-example-a``` invokes ```polaris-circuitbreaker-example-b```. +This example contains callee-service```polaris-circuitbreaker-callee-service```、```polaris-circuitbreaker-callee-service2```and caller-service```polaris-circuitbreaker-feign-example```、```polaris-circuitbreaker-gateway-example```、```polaris-circuitbreaker-webclient-example```. ## Instruction @@ -21,58 +21,46 @@ spring: address: ${ip}:${port} ``` -###Launching Example +### Launching Example -###Launching Polaris Backend Service +#### Launching Polaris Backend Service Reference to [Polaris Getting Started](https://github.com/PolarisMesh/polaris#getting-started) -####Launching Application +#### Launching callee service -Note, because verification is needed for circuit-break feature, therefore, one needs to deploy more than two invoked services (two deployment in this example) +Launching```polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service```、```polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2``` -Launching```spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a```'s ServiceA and ```spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b```'s ServiceB -note, Service B needs to launch two. One can adjust the port on the same machine. +#### Launching caller service -Two Services B's ```com.tencent.cloud.polaris.circuitbreaker.example.ServiceBController.info``` logics are different. One returns normally, one is abnormal. +##### Launching Feign and Verify -- Maven Package Launching +Launching```polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example```. -Execute under ```spring-cloud-tencent-examples/polaris-discovery-example``` +Sending request`curl --location --request GET 'http://127.0.0.1:48080/example/service/a/getBServiceInfo/fallbackFromPolaris'`, Verify circuit breaker and fallback from Polaris-server. -note, Service B needs to launch two. One can adjust the port on the same machine. +Sending request`curl --location --request GET 'http://127.0.0.1:48080/example/service/a/getBServiceInfo/fallbackFromCode'`, Verify circuit breaker and fallback from code. -Two Services B's com.tencent.cloud.polaris.circuitbreaker.example.ServiceBController.info logics are different. One returns normally, one is abnormal. +##### Launching RestTemplate and Verify -```sh -mvn clean package -``` +Launching```polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example```. -Then under ``polaris-circuitbreaker-example-a``` and ``polaris-circuitbreaker-example-b``` find the package that generated jar, and run it +Sending request`curl --location --request GET 'http://127.0.0.1:48080/example/service/a/getBServiceInfo/fallbackFromPolaris'`, Verify circuit breaker and fallback from Polaris-server. -``` -java -jar ${app.jar} -``` +Sending request`curl --location --request GET 'http://127.0.0.1:48080/example/service/a/getBServiceInfo/fallbackFromCode'`, Verify circuit breaker and fallback from code. -Launch application, change ${app.jar} to jar's package name +##### Launching WebClient and Verify -##Verify +Launching```polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example```。 -####Feign Invoke +Sending request`curl --location --request GET 'http://127.0.0.1:48080/example/service/a/getBServiceInfo'`, Verify circuit breaker and fallback from code. -Execute the following orders to invoke Feign, the logic is ```ServiceB``` has an abnormal signal +##### Launching SCG and Verify -```shell -curl -L -X GET 'localhost:48080/example/service/a/getBServiceInfo' -``` +Launching```polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example```。 -Expected return condition: +Sending request`curl --location --request GET 'http://127.0.0.1:48080/polaris-circuitbreaker-callee-service/example/service/b/info'`, Verify circuit breaker and fallback from code. -when appear - -``` -trigger the refuse for service b -``` +Changing```polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/resources/bootstrap.yml```, delete local fallback and restart, Verify circuit breaker and fallback from Polaris-server. -it means the request signals abnormal ServiceB, and will ciruitbreak this instance, the later requests will return normally. diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service/pom.xml similarity index 91% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/pom.xml rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service/pom.xml index 01dd7904a..e1ee856b3 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/pom.xml +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service/pom.xml @@ -10,8 +10,8 @@ </parent> <modelVersion>4.0.0</modelVersion> - <artifactId>polaris-circuitbreaker-example-b</artifactId> - <name>Polaris Circuit Breaker Example B</name> + <artifactId>polaris-circuitbreaker-callee-service</artifactId> + <name>Polaris Circuit Breaker Callee Example</name> <dependencies> <dependency> diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceB.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceB.java similarity index 100% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceB.java rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceB.java diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java similarity index 80% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java index 128ea5e4d..8628db06a 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java @@ -40,4 +40,16 @@ public class ServiceBController { public String info() { return "hello world ! I'm a service B1"; } + + @GetMapping("/health") + public String health() { + System.out.println("health check: 200 instance"); + return "hello world ! I'm a service B1"; + } + + @GetMapping("/health-svc") + public String healthsvc() { + System.out.println("health-svc check: 200 instance"); + return "hello world ! I'm a service B1"; + } } diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service/src/main/resources/bootstrap.yml similarity index 81% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/resources/bootstrap.yml rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service/src/main/resources/bootstrap.yml index d6945b5b4..5fa99c252 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service/src/main/resources/bootstrap.yml @@ -2,7 +2,7 @@ server: port: 48081 spring: application: - name: polaris-circuitbreaker-example-b + name: polaris-circuitbreaker-callee-service cloud: polaris: address: grpc://183.47.111.80:8091 diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2/pom.xml similarity index 92% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/pom.xml rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2/pom.xml index 340de2286..152aeef96 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/pom.xml +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2/pom.xml @@ -10,7 +10,8 @@ </parent> <modelVersion>4.0.0</modelVersion> - <artifactId>polaris-circuitbreaker-example-b2</artifactId> + <artifactId>polaris-circuitbreaker-callee-service2</artifactId> + <name>Polaris Circuit Breaker Callee Example 2</name> <dependencies> <dependency> diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceB2.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceB2.java similarity index 100% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceB2.java rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceB2.java diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceBController.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceBController.java new file mode 100644 index 000000000..2d5287c3a --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceBController.java @@ -0,0 +1,98 @@ +/* + * 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.ciruitbreaker.example; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +/** + * Service B Controller. + * + * @author Haotian Zhang + */ +@RestController +@RequestMapping("/example/service/b") +public class ServiceBController { + + private static final Logger LOG = LoggerFactory.getLogger(ServiceBController.class); + + private boolean ifBadGateway = true; + + private boolean ifDelay = true; + + @GetMapping("/setBadGateway") + public void setBadGateway(@RequestParam boolean param) { + if (param) { + LOG.info("info is set to return HttpStatus.BAD_GATEWAY."); + } + else { + LOG.info("info is set to return HttpStatus.OK."); + } + this.ifBadGateway = param; + } + + @GetMapping("/setDelay") + public void setDelay(@RequestParam boolean param) { + if (param) { + LOG.info("info is set to delay 100ms."); + } + else { + LOG.info("info is set to no delay."); + } + this.ifDelay = param; + } + + /** + * Get service information. + * + * @return service information + */ + @GetMapping("/info") + public ResponseEntity<String> info() throws InterruptedException { + if (ifBadGateway) { + return new ResponseEntity<>("failed for call my service", HttpStatus.BAD_GATEWAY); + } + if (ifDelay) { + Thread.sleep(100); + } + return new ResponseEntity<>("hello world ! I'm a service B2", HttpStatus.OK); + } + + @GetMapping("/health") + @ResponseStatus(value = HttpStatus.BAD_GATEWAY, reason = "failed for call my service") + public String health() { + System.out.println("health check: 502 instance"); + return "hello world ! I'm a service B1"; + } + + @GetMapping("/health-svc") + @ResponseStatus(value = HttpStatus.BAD_GATEWAY, reason = "failed for call my service") + public String healthsvc() { + System.out.println("health-svc check: 502 instance"); + return "hello world ! I'm a service B1"; + } +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2/src/main/resources/bootstrap.yml similarity index 81% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/resources/bootstrap.yml rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2/src/main/resources/bootstrap.yml index 5ef89145c..54cb21355 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-callee-service2/src/main/resources/bootstrap.yml @@ -2,7 +2,7 @@ server: port: 48082 spring: application: - name: polaris-circuitbreaker-example-b + name: polaris-circuitbreaker-callee-service cloud: polaris: address: grpc://183.47.111.80:8091 diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/ESAPI.properties b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/ESAPI.properties deleted file mode 100644 index 32df629d9..000000000 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/ESAPI.properties +++ /dev/null @@ -1,14 +0,0 @@ -ESAPI.printProperties=true -ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder -ESAPI.Logger=org.owasp.esapi.logging.slf4j.Slf4JLogFactory - -Encoder.AllowMultipleEncoding=false -Encoder.AllowMixedEncoding=false -Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec - -Logger.LogEncodingRequired=false -Logger.UserInfo=false -Logger.ClientInfo=false -Logger.ApplicationName=ExampleApplication -Logger.LogApplicationName=false -Logger.LogServerIP=false diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/bootstrap.yml deleted file mode 100644 index ad35a87da..000000000 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/bootstrap.yml +++ /dev/null @@ -1,46 +0,0 @@ -server: - port: 48080 -spring: - application: - name: polaris-circuitbreaker-example-a - cloud: - polaris: - address: grpc://183.47.111.80:8091 - namespace: default - enabled: true - circuitbreaker: - enabled: true - stat: - enabled: true - port: 28081 - tencent: - rpc-enhancement: - enabled: true - reporter: - ignore-internal-server-error: true - series: server_error - statuses: gateway_timeout, bad_gateway, service_unavailable - -feign: - hystrix: - enabled: true - compression: - request: - enabled: false - mime-types: text/xml,application/xml,application/json - min-request-size: 2048 - response: - enabled: false -ribbon: - polaris: - enabled: true - MaxAutoRetries: 1 - MaxAutoRetriesNextServer: 2 - OkToRetryOnAllOperations: false - ConnectionTimeout: 1000 - ReadTimeout: 1000 - eager-load: - enabled: on - -serivceB: - url: http://localhost:48081 diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/pom.xml similarity index 86% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/pom.xml rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/pom.xml index 70351a785..9093881bb 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/pom.xml +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/pom.xml @@ -10,8 +10,8 @@ </parent> <modelVersion>4.0.0</modelVersion> - <artifactId>polaris-circuitbreaker-example-a</artifactId> - <name>Polaris Circuit Breaker Example A</name> + <artifactId>polaris-circuitbreaker-feign-example</artifactId> + <name>Polaris Circuit Breaker Feign Example</name> <dependencies> <dependency> @@ -24,11 +24,6 @@ <artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId> </dependency> - <dependency> - <groupId>com.tencent.cloud</groupId> - <artifactId>spring-cloud-starter-tencent-polaris-circuitbreaker</artifactId> - </dependency> - <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> @@ -36,12 +31,12 @@ <dependency> <groupId>org.springframework.cloud</groupId> - <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> + <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <dependency> - <groupId>org.owasp.esapi</groupId> - <artifactId>esapi</artifactId> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-starter-tencent-polaris-circuitbreaker</artifactId> </dependency> </dependencies> diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderB.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ProviderB.java similarity index 85% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderB.java rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ProviderB.java index f0c05217a..63dbbdb17 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderB.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ProviderB.java @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.polaris.circuitbreaker.example; +package com.tencent.cloud.polaris.circuitbreaker.feign.example; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @@ -23,9 +23,9 @@ import org.springframework.web.bind.annotation.GetMapping; /** * Circuit breaker example callee provider. * - * @author Haotian Zhang + * @author sean yu */ -@FeignClient(name = "polaris-circuitbreaker-example-b", fallback = ProviderBFallback.class) +@FeignClient(name = "polaris-circuitbreaker-callee-service", contextId = "fallback-from-polaris") public interface ProviderB { /** diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderBFallback.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ProviderBFallback.java similarity index 81% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderBFallback.java rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ProviderBFallback.java index bf47d49dd..419b18cfc 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderBFallback.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ProviderBFallback.java @@ -15,20 +15,20 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.polaris.circuitbreaker.example; +package com.tencent.cloud.polaris.circuitbreaker.feign.example; import org.springframework.stereotype.Component; /** * Circuit breaker example callee fallback. * - * @author Haotian Zhang + * @author sean yu */ @Component -public class ProviderBFallback implements ProviderB { +public class ProviderBFallback implements ProviderBWithFallback { @Override public String info() { - return "trigger the refuse for service b"; + return "fallback: trigger the refuse for service b"; } } diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ProviderBWithFallback.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ProviderBWithFallback.java new file mode 100644 index 000000000..194ac1305 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ProviderBWithFallback.java @@ -0,0 +1,39 @@ +/* + * 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.feign.example; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * ProviderBWithFallback. + * + * @author sean yu + */ +@FeignClient(name = "polaris-circuitbreaker-callee-service", contextId = "fallback-from-code", fallback = ProviderBFallback.class) +public interface ProviderBWithFallback { + + /** + * Get info of service B. + * + * @return info of service B + */ + @GetMapping("/example/service/b/info") + String info(); + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ServiceAController.java similarity index 58% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ServiceAController.java index 04570498c..30aa94c49 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ServiceAController.java @@ -15,21 +15,18 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.polaris.circuitbreaker.example; +package com.tencent.cloud.polaris.circuitbreaker.feign.example; -import org.owasp.esapi.ESAPI; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; /** * Circuit breaker example caller controller. * - * @author Haotian Zhang + * @author sean yu */ @RestController @RequestMapping("/example/service/a") @@ -39,37 +36,24 @@ public class ServiceAController { private ProviderB polarisServiceB; @Autowired - private RestTemplate restTemplate; + private ProviderBWithFallback providerBWithFallback; /** * Get info of Service B by Feign. * @return info of Service B */ - @GetMapping("/getBServiceInfo") - public String getBServiceInfo() { - return polarisServiceB.info(); - } - - @GetMapping("/getBServiceInfoByRestTemplate") - public String getBServiceInfoByRestTemplate() { - return restTemplate.getForObject("http://polaris-circuitbreaker-example-b/example/service/b/info", String.class); + @GetMapping("/getBServiceInfo/fallbackFromCode") + public String getBServiceInfoFallbackFromCode() { + return providerBWithFallback.info(); } /** - * Get info of Service B by RestTemplate. + * Get info of Service B by Feign. * @return info of Service B */ - @GetMapping("/testRest") - public String testRest() { - ResponseEntity<String> entity = restTemplate.getForEntity( - "http://polaris-circuitbreaker-example-b/example/service/b/info", - String.class); - String response = entity.getBody(); - return cleanXSS(response); + @GetMapping("/getBServiceInfo/fallbackFromPolaris") + public String getBServiceInfoFallbackFromPolaris() { + return polarisServiceB.info(); } - private String cleanXSS(String str) { - str = ESAPI.encoder().encodeForHTML(str); - return str; - } } diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ServiceAFeign.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ServiceAFeign.java new file mode 100644 index 000000000..f6a012bb0 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/example/ServiceAFeign.java @@ -0,0 +1,38 @@ +/* + * 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.feign.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * Circuit breaker example caller application. + * + * @author sean yu + */ +@SpringBootApplication +@EnableFeignClients +public class ServiceAFeign { + + public static void main(String[] args) { + SpringApplication.run(ServiceAFeign.class, args); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/resources/application.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/resources/application.yml new file mode 100644 index 000000000..bf20a3b2b --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-feign-example/src/main/resources/application.yml @@ -0,0 +1,22 @@ +server: + port: 48080 +spring: + application: + name: polaris-circuitbreaker-feign-example + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + enabled: true + loadbalancer: + enabled: true + +feign: + circuitbreaker: + enabled: true + +logging: + level: + root: info + com.tencent.cloud: debug + diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/pom.xml new file mode 100644 index 000000000..342df6ae1 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/pom.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>polaris-circuitbreaker-example</artifactId> + <groupId>com.tencent.cloud</groupId> + <version>${revision}</version> + <relativePath>../pom.xml</relativePath> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>polaris-circuitbreaker-gateway-example</artifactId> + <name>Polaris Circuit Breaker Gateway Example</name> + + <dependencies> + <dependency> + <artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId> + <groupId>com.tencent.cloud</groupId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-starter-tencent-polaris-circuitbreaker</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-starter-tencent-polaris-router</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-tencent-gateway-plugin</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-tencent-featureenv-plugin</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-gateway</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>3.2.0</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceBController.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/gateway/example/FallbackController.java similarity index 59% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceBController.java rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/gateway/example/FallbackController.java index 384167816..c0d964b56 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b2/src/main/java/com/tencent/cloud/polaris/ciruitbreaker/example/ServiceBController.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/gateway/example/FallbackController.java @@ -13,34 +13,25 @@ * 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.ciruitbreaker.example; +package com.tencent.cloud.polaris.circuitbreaker.gateway.example; + +import reactor.core.publisher.Mono; -import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; /** - * Service B Controller. + * FallbackController. * - * @author Haotian Zhang + * sean yu */ @RestController -@RequestMapping("/example/service/b") -public class ServiceBController { +public class FallbackController { - /** - * Get service information. - * - * @return service information - */ - @GetMapping("/info") - @ResponseStatus(value = HttpStatus.BAD_GATEWAY, reason = "failed for call my service") - public String info() { - return "failed for call service B2"; + @GetMapping("/polaris-fallback") + Mono<String> getFallback() { + return Mono.just("fallback: trigger the refuse for service b"); } } diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/gateway/example/GatewayScgApplication.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/gateway/example/GatewayScgApplication.java new file mode 100644 index 000000000..ea042b2f9 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/gateway/example/GatewayScgApplication.java @@ -0,0 +1,35 @@ +/* + * 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.gateway.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * SCG application. + * + * @author sean yu + */ +@SpringBootApplication +public class GatewayScgApplication { + + public static void main(String[] args) { + SpringApplication.run(GatewayScgApplication.class, args); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..a7bdb3f4b --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-gateway-example/src/main/resources/bootstrap.yml @@ -0,0 +1,59 @@ +server: + session-timeout: 1800 + port: 48080 +spring: + application: + name: GatewayScgService + cloud: + tencent: + plugin: + scg: + staining: + enabled: true + rule-staining: + enabled: true + router: + feature-env: + enabled: true + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + enabled: true + gateway: + discovery: + locator: + enabled: true + 'predicates[0]': + name: Path + args: + patterns: '''/'' + serviceId + ''/**''' + 'filters[0]': + name: RewritePath + args: + regexp: '''/'' + serviceId + ''/(?<remaining>.*)''' + replacement: '''/$\{remaining}''' + 'filters[1]': + name: CircuitBreaker + args: + # statusCodes 缺省时会自动识别 "5**" 为错误 +# statusCodes: '''404,5**''' + # fallbackUri 缺省时会在熔断触发后拉取 plaris server 配置的降级作为 response + fallbackUri: '''forward:/polaris-fallback''' +# routes: +# - id: polaris-circuitbreaker-callee-service +# uri: lb://polaris-circuitbreaker-callee-service +# predicates: +# - Path=/polaris-circuitbreaker-callee-service/** +# filters: +# - StripPrefix=1 +# - name: CircuitBreaker +# args: +# statusCodes: 502 +# fallbackUri: forward:/polaris-fallback +logging: + level: + root: info + com.tencent.polaris.discovery.client.flow.RegisterFlow: off + com.tencent.polaris.plugins.registry: off + com.tencent.cloud: debug + diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/pom.xml new file mode 100644 index 000000000..dc59e6607 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/pom.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>polaris-circuitbreaker-example</artifactId> + <groupId>com.tencent.cloud</groupId> + <version>${revision}</version> + <relativePath>../pom.xml</relativePath> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>polaris-circuitbreaker-resttemplate-example</artifactId> + <name>Polaris Circuit Breaker RestTemplate Example</name> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-loadbalancer</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-starter-tencent-polaris-circuitbreaker</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>3.2.0</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/example/CustomFallback.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/example/CustomFallback.java new file mode 100644 index 000000000..a7f5f1a65 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/example/CustomFallback.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.polaris.circuitbreaker.resttemplate.example; + +import java.util.HashMap; + +import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerFallback; +import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerHttpResponse; + +import org.springframework.stereotype.Component; + +/** + * CustomFallback. + * + * @author sean yu + */ +@Component +public class CustomFallback implements PolarisCircuitBreakerFallback { + @Override + public PolarisCircuitBreakerHttpResponse fallback() { + return new PolarisCircuitBreakerHttpResponse( + 200, + new HashMap<String, String>() {{ + put("Content-Type", "application/json"); + }}, + "{\"msg\": \"this is a fallback class\"}"); + } +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/example/ServiceAController.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/example/ServiceAController.java new file mode 100644 index 000000000..d17744ce3 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/example/ServiceAController.java @@ -0,0 +1,74 @@ +/* + * 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.resttemplate.example; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +/** + * Circuit breaker example caller controller. + * + * @author sean yu + */ +@RestController +@RequestMapping("/example/service/a") +public class ServiceAController { + + @Autowired + @Qualifier("defaultRestTemplate") + private RestTemplate defaultRestTemplate; + + @Autowired + @Qualifier("restTemplateFallbackFromPolaris") + private RestTemplate restTemplateFallbackFromPolaris; + + @Autowired + @Qualifier("restTemplateFallbackFromCode") + private RestTemplate restTemplateFallbackFromCode; + + @Autowired + private CircuitBreakerFactory circuitBreakerFactory; + + @GetMapping("/getBServiceInfo") + public String getBServiceInfo() { + return circuitBreakerFactory + .create("polaris-circuitbreaker-callee-service#/example/service/b/info") + .run(() -> + defaultRestTemplate.getForObject("/example/service/b/info", String.class), + throwable -> "trigger the refuse for service b" + ); + } + + @GetMapping("/getBServiceInfo/fallbackFromPolaris") + public ResponseEntity<String> getBServiceInfoFallback() { + return restTemplateFallbackFromPolaris.getForEntity("/example/service/b/info", String.class); + } + + @GetMapping("/getBServiceInfo/fallbackFromCode") + public ResponseEntity<String> getBServiceInfoFallbackClass() { + return restTemplateFallbackFromCode.getForEntity("/example/service/b/info", String.class); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/example/ServiceAResTemplate.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/example/ServiceAResTemplate.java new file mode 100644 index 000000000..fb6495912 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/example/ServiceAResTemplate.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.resttemplate.example; + +import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriBuilderFactory; + +/** + * Circuit breaker example caller application. + * + * @author sean yu + */ +@SpringBootApplication +public class ServiceAResTemplate { + + public static void main(String[] args) { + SpringApplication.run(ServiceAResTemplate.class, args); + } + + @Bean + @LoadBalanced + public RestTemplate defaultRestTemplate() { + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://polaris-circuitbreaker-callee-service"); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + return restTemplate; + } + + @Bean + @LoadBalanced + @PolarisCircuitBreaker + public RestTemplate restTemplateFallbackFromPolaris() { + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://polaris-circuitbreaker-callee-service"); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + return restTemplate; + } + + @Bean + @LoadBalanced + @PolarisCircuitBreaker(fallbackClass = CustomFallback.class) + public RestTemplate restTemplateFallbackFromCode() { + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://polaris-circuitbreaker-callee-service"); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + return restTemplate; + } + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..720c3df80 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-resttemplate-example/src/main/resources/bootstrap.yml @@ -0,0 +1,20 @@ +server: + port: 48080 +spring: + application: + name: polaris-circuitbreaker-resttemplate-example + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + enabled: true + loadbalancer: + enabled: true + circuitbreaker: + enabled: true + +logging: + level: + root: info + com.tencent.cloud: debug + diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/pom.xml new file mode 100644 index 000000000..efb1089ae --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/pom.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>polaris-circuitbreaker-example</artifactId> + <groupId>com.tencent.cloud</groupId> + <version>${revision}</version> + <relativePath>../pom.xml</relativePath> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>polaris-circuitbreaker-webclient-example</artifactId> + <name>Polaris Circuit Breaker WebClient Example</name> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-webflux</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-loadbalancer</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-starter-tencent-polaris-circuitbreaker</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>3.2.0</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/webclient/example/ServiceAController.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/webclient/example/ServiceAController.java new file mode 100644 index 000000000..72f3c80d5 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/webclient/example/ServiceAController.java @@ -0,0 +1,59 @@ +/* + * 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.webclient.example; + +import reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Circuit breaker example caller controller. + * + * @author sean yu + */ +@RestController +@RequestMapping("/example/service/a") +public class ServiceAController { + + @Autowired + private ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory; + + @Autowired + private WebClient.Builder webClientBuilder; + + @GetMapping("/getBServiceInfo") + public Mono<String> getBServiceInfo() { + return webClientBuilder + .build() + .get() + .uri("/example/service/b/info") + .retrieve() + .bodyToMono(String.class) + .transform(it -> + reactiveCircuitBreakerFactory + .create("polaris-circuitbreaker-callee-service#/example/service/b/info") + .run(it, throwable -> Mono.just("fallback: trigger the refuse for service b")) + ); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceA.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/webclient/example/ServiceAWebClient.java similarity index 75% rename from spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceA.java rename to spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/webclient/example/ServiceAWebClient.java index 3db7df8f3..8104ae873 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceA.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/webclient/example/ServiceAWebClient.java @@ -16,31 +16,30 @@ * */ -package com.tencent.cloud.polaris.circuitbreaker.example; +package com.tencent.cloud.polaris.circuitbreaker.webclient.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; /** * Circuit breaker example caller application. * - * @author Haotian Zhang + * @author sean yu */ @SpringBootApplication -@EnableFeignClients -public class ServiceA { +public class ServiceAWebClient { public static void main(String[] args) { - SpringApplication.run(ServiceA.class, args); + SpringApplication.run(ServiceAWebClient.class, args); } - @Bean @LoadBalanced - public RestTemplate restTemplate() { - return new RestTemplate(); + @Bean + WebClient.Builder webClientBuilder() { + return WebClient.builder() + .baseUrl("http://polaris-circuitbreaker-callee-service"); } } diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..7e75556f3 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-webclient-example/src/main/resources/bootstrap.yml @@ -0,0 +1,20 @@ +server: + port: 48080 +spring: + application: + name: polaris-circuitbreaker-webclient-example + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + enabled: true + loadbalancer: + enabled: true + circuitbreaker: + enabled: true + +logging: + level: + root: info + com.tencent.cloud: debug + diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-zuul-example/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-zuul-example/pom.xml new file mode 100644 index 000000000..670565bc8 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-zuul-example/pom.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>polaris-circuitbreaker-example</artifactId> + <groupId>com.tencent.cloud</groupId> + <version>${revision}</version> + <relativePath>../pom.xml</relativePath> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>polaris-circuitbreaker-zuul-example</artifactId> + <name>Polaris Circuit Breaker Zuul Example</name> + + <dependencies> + <dependency> + <artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId> + <groupId>com.tencent.cloud</groupId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-starter-tencent-polaris-circuitbreaker</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-netflix-zuul</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-starter-tencent-polaris-ratelimit</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-starter-tencent-polaris-router</artifactId> + </dependency> + + <dependency> + <groupId>com.tencent.cloud</groupId> + <artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>3.2.0</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-zuul-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/example/ConsumerFallback.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-zuul-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/example/ConsumerFallback.java new file mode 100644 index 000000000..dd8147311 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-zuul-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/example/ConsumerFallback.java @@ -0,0 +1,89 @@ +/* + * 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.zuul.example; + + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.stereotype.Component; + +/** + * Custom fallback. + * + * @author Haotian Zhang + */ +@Component +public class ConsumerFallback implements FallbackProvider { + + private static final Logger LOG = LoggerFactory.getLogger(ConsumerFallback.class); + + @Override + public String getRoute() { + return "polaris-circuitbreaker-callee-service"; + } + + @Override + public ClientHttpResponse fallbackResponse(String route, Throwable cause) { + if (cause != null && cause.getCause() != null) { + LOG.error("Fallback is called.", cause); + } + + return new ClientHttpResponse() { + @Override + public HttpStatus getStatusCode() { + return HttpStatus.OK; + } + + @Override + public int getRawStatusCode() { + return 200; + } + + @Override + public String getStatusText() { + return "OK"; + } + + @Override + public void close() { + + } + + @Override + public InputStream getBody() { + return new ByteArrayInputStream("zuul custom fallback".getBytes()); + } + + @Override + public HttpHeaders getHeaders() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.TEXT_PLAIN); + return httpHeaders; + } + }; + } +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-zuul-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/example/ServiceAZuul.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-zuul-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/example/ServiceAZuul.java new file mode 100644 index 000000000..ba96e7bb8 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-zuul-example/src/main/java/com/tencent/cloud/polaris/circuitbreaker/zuul/example/ServiceAZuul.java @@ -0,0 +1,37 @@ +/* + * 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.zuul.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.zuul.EnableZuulProxy; + +/** + * Zuul application. + * + * @author Haotian Zhang + */ +@SpringBootApplication +@EnableZuulProxy +public class ServiceAZuul { + + public static void main(String[] args) { + SpringApplication.run(ServiceAZuul.class, args); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-zuul-example/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-zuul-example/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..8b871b721 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-zuul-example/src/main/resources/bootstrap.yml @@ -0,0 +1,23 @@ +server: + session-timeout: 1800 + port: 48080 +spring: + application: + name: polaris-circuitbreaker-zuul-example + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + circuitbreaker: + enabled: true +zuul: + routes: + GatewayCalleeService: + serviceId: polaris-circuitbreaker-callee-service + path: /polaris-circuitbreaker-callee-service/** +logging: + level: + root: info + com.tencent.polaris.discovery.client.flow.RegisterFlow: off + com.tencent.polaris.plugins.registry: off + com.tencent.cloud: debug diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/pom.xml index 6b5daa263..032c6e28f 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/pom.xml +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/pom.xml @@ -15,8 +15,12 @@ <packaging>pom</packaging> <modules> - <module>polaris-circuitbreaker-example-a</module> - <module>polaris-circuitbreaker-example-b</module> - <module>polaris-circuitbreaker-example-b2</module> + <module>polaris-circuitbreaker-feign-example</module> + <module>polaris-circuitbreaker-gateway-example</module> + <module>polaris-circuitbreaker-zuul-example</module> + <module>polaris-circuitbreaker-resttemplate-example</module> + <module>polaris-circuitbreaker-webclient-example</module> + <module>polaris-circuitbreaker-callee-service</module> + <module>polaris-circuitbreaker-callee-service2</module> </modules> </project> diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/pom.xml b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/pom.xml index 19179dac1..873b5c81e 100644 --- a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/pom.xml +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/pom.xml @@ -30,14 +30,14 @@ </dependency> <!-- <dependency>--> - <!-- <groupId>org.springframework.cloud</groupId>--> - <!-- <artifactId>spring-cloud-starter-consul-discovery</artifactId>--> + <!-- <groupId>com.tencent.polaris</groupId>--> + <!-- <artifactId>connector-consul</artifactId>--> <!-- </dependency>--> - <!-- <dependency>--> - <!-- <groupId>org.springframework.cloud</groupId>--> - <!-- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>--> - <!-- </dependency>--> + <!-- <dependency>--> + <!-- <groupId>com.tencent.polaris</groupId>--> + <!-- <artifactId>connector-nacos</artifactId>--> + <!-- </dependency>--> </dependencies> <build> diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/pom.xml b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/pom.xml index 1d370e7f5..aadcea9ee 100644 --- a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/pom.xml +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/pom.xml @@ -28,15 +28,15 @@ <artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId> </dependency> - <!-- <dependency>--> - <!-- <groupId>org.springframework.cloud</groupId>--> - <!-- <artifactId>spring-cloud-starter-consul-discovery</artifactId>--> - <!-- </dependency>--> + <!-- <dependency>--> + <!-- <groupId>com.tencent.polaris</groupId>--> + <!-- <artifactId>connector-consul</artifactId>--> + <!-- </dependency>--> - <!-- <dependency>--> - <!-- <groupId>org.springframework.cloud</groupId>--> - <!-- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>--> - <!-- </dependency>--> + <!-- <dependency>--> + <!-- <groupId>com.tencent.polaris</groupId>--> + <!-- <artifactId>connector-nacos</artifactId>--> + <!-- </dependency>--> </dependencies> <build> diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml index f7cbb1e6f..7788c0a2d 100644 --- a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml @@ -18,6 +18,11 @@ <artifactId>spring-boot-starter-web</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-webflux</artifactId> + </dependency> + <dependency> <groupId>com.tencent.cloud</groupId> <artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId> diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/BusinessController.java b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/BusinessController.java index e33c6f708..902d6681f 100644 --- a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/BusinessController.java +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/BusinessController.java @@ -17,15 +17,22 @@ package com.tencent.cloud.ratelimit.example.service.callee; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -33,6 +40,8 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.HttpClientErrorException.TooManyRequests; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; /** * Rate limit controller. @@ -49,6 +58,8 @@ public class BusinessController { private final AtomicLong lastTimestamp = new AtomicLong(0); @Autowired private RestTemplate restTemplate; + @Autowired + private WebClient.Builder webClientBuilder; @Value("${spring.application.name}") private String appName; @@ -62,6 +73,44 @@ public class BusinessController { return "hello world for ratelimit service " + index.incrementAndGet(); } + @GetMapping("/info/webclient") + public Mono<String> infoWebClient() { + return Mono.just("hello world for ratelimit service " + index.incrementAndGet()); + } + + @GetMapping("/invoke/webclient") + public String invokeInfoWebClient() throws InterruptedException, ExecutionException { + StringBuffer builder = new StringBuffer(); + WebClient webClient = webClientBuilder.baseUrl("http://" + appName).build(); + List<Mono<String>> monoList = new ArrayList<>(); + for (int i = 0; i < 30; i++) { + Mono<String> response = webClient.get() + .uri(uriBuilder -> uriBuilder + .path("/business/info/webclient") + .queryParam("yyy", "yyy") + .build() + ) + .header("xxx", "xxx") + .retrieve() + .bodyToMono(String.class) + .doOnSuccess(s -> builder.append(s + "\n")) + .doOnError(e -> { + if (e instanceof WebClientResponseException) { + if (((WebClientResponseException) e).getRawStatusCode() == 429) { + builder.append("TooManyRequests ").append(index.incrementAndGet() + "\n"); + } + } + }) + .onErrorReturn(""); + monoList.add(response); + } + for (Mono<String> mono : monoList) { + mono.toFuture().get(); + } + index.set(0); + return builder.toString(); + } + /** * Get information 30 times per 1 second. * @@ -75,19 +124,31 @@ public class BusinessController { for (int i = 0; i < 30; i++) { new Thread(() -> { try { - ResponseEntity<String> entity = restTemplate.getForEntity("http://" + appName + "/business/info", - String.class); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add("xxx", "xxx"); + ResponseEntity<String> entity = restTemplate.exchange( + "http://" + appName + "/business/info?yyy={yyy}", + HttpMethod.GET, + new HttpEntity<>(httpHeaders), + String.class, + "yyy" + ); builder.append(entity.getBody() + "\n"); } catch (RestClientException e) { if (e instanceof TooManyRequests) { - builder.append("TooManyRequests " + index.incrementAndGet() + "\n"); + builder.append("TooManyRequests ").append(index.incrementAndGet() + "\n"); } else { throw e; } } - count.countDown(); + catch (Exception e) { + e.printStackTrace(); + } + finally { + count.countDown(); + } }).start(); } count.await(); diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/CustomLabelResolverReactive.java b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/CustomLabelResolverReactive.java new file mode 100644 index 000000000..eb6e01b03 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/CustomLabelResolverReactive.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.ratelimit.example.service.callee; + +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver; + +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; + +/** + * resolver custom label from request. + * + * @author sean yu + */ +@Component +public class CustomLabelResolverReactive implements PolarisRateLimiterLabelReactiveResolver { + @Override + public Map<String, String> resolve(ServerWebExchange exchange) { + // rate limit by some request params. such as query params, headers .. + + Map<String, String> labels = new HashMap<>(); + labels.put("user", "zhangsan"); + + return labels; + } +} diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/RateLimitCalleeService.java b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/RateLimitCalleeService.java index 12a9270cc..c8a49d2a7 100644 --- a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/RateLimitCalleeService.java +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/RateLimitCalleeService.java @@ -22,6 +22,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; /** * Rate limit application. @@ -40,4 +41,11 @@ public class RateLimitCalleeService { public RestTemplate restTemplate() { return new RestTemplate(); } + + + @LoadBalanced + @Bean + WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } } diff --git a/spring-cloud-tencent-polaris-loadbalancer/src/test/java/com/tencent/cloud/polaris/loadbalancer/config/PolarisRibbonClientConfigurationTest.java b/spring-cloud-tencent-polaris-loadbalancer/src/test/java/com/tencent/cloud/polaris/loadbalancer/config/PolarisRibbonClientConfigurationTest.java index 15d56b312..774a3c6f0 100644 --- a/spring-cloud-tencent-polaris-loadbalancer/src/test/java/com/tencent/cloud/polaris/loadbalancer/config/PolarisRibbonClientConfigurationTest.java +++ b/spring-cloud-tencent-polaris-loadbalancer/src/test/java/com/tencent/cloud/polaris/loadbalancer/config/PolarisRibbonClientConfigurationTest.java @@ -24,6 +24,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import static com.tencent.polaris.test.common.Consts.PORT; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; import static org.assertj.core.api.Assertions.assertThat; /** @@ -41,6 +43,9 @@ public class PolarisRibbonClientConfigurationTest { .withConfiguration(AutoConfigurations.of( TestApplication.class, PolarisRibbonClientConfiguration.class)) + .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) + .withPropertyValues("server.port=" + PORT) + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081") .run(context -> { assertThat(context).hasSingleBean(PolarisRibbonClientConfiguration.class); assertThat(context).hasSingleBean(PolarisLoadBalancer.class); diff --git a/spring-cloud-tencent-rpc-enhancement/pom.xml b/spring-cloud-tencent-rpc-enhancement/pom.xml index 65f9c45ff..f9fb98732 100644 --- a/spring-cloud-tencent-rpc-enhancement/pom.xml +++ b/spring-cloud-tencent-rpc-enhancement/pom.xml @@ -42,7 +42,6 @@ <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-loadbalancer</artifactId> - <optional>true</optional> </dependency> <dependency> @@ -52,20 +51,32 @@ </dependency> <dependency> - <groupId>com.tencent.polaris</groupId> - <artifactId>polaris-test-common</artifactId> - <scope>test</scope> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gateway-server</artifactId> + <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> - <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> - <scope>test</scope> + <artifactId>spring-cloud-starter-netflix-zuul</artifactId> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-webflux</artifactId> + <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>com.tencent.polaris</groupId> + <artifactId>polaris-test-common</artifactId> <scope>test</scope> </dependency> 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..d177aad8b 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 @@ -17,18 +17,38 @@ package com.tencent.cloud.rpc.enhancement; +import java.io.UnsupportedEncodingException; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URLDecoder; 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.common.constant.RouterConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.RequestLabelUtils; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.plugin.circuitbreaker.ResourceStat; +import com.tencent.polaris.api.plugin.circuitbreaker.entity.InstanceResource; +import com.tencent.polaris.api.plugin.circuitbreaker.entity.Resource; +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 org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; +import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; import static org.springframework.http.HttpStatus.BAD_GATEWAY; import static org.springframework.http.HttpStatus.BANDWIDTH_LIMIT_EXCEEDED; import static org.springframework.http.HttpStatus.GATEWAY_TIMEOUT; @@ -48,22 +68,85 @@ import static org.springframework.http.HttpStatus.VARIANT_ALSO_NEGOTIATES; * @author <a href="mailto:iskp.me@gmail.com">Elve.Xu</a> 2022-07-11 */ public abstract class AbstractPolarisReporterAdapter { - private static final Logger LOG = LoggerFactory.getLogger(AbstractPolarisReporterAdapter.class); private static final List<HttpStatus> HTTP_STATUSES = toList(NOT_IMPLEMENTED, BAD_GATEWAY, SERVICE_UNAVAILABLE, GATEWAY_TIMEOUT, HTTP_VERSION_NOT_SUPPORTED, VARIANT_ALSO_NEGOTIATES, INSUFFICIENT_STORAGE, LOOP_DETECTED, BANDWIDTH_LIMIT_EXCEEDED, NOT_EXTENDED, NETWORK_AUTHENTICATION_REQUIRED); + protected final RpcEnhancementReporterProperties reportProperties; + protected final SDKContext context; + /** * Constructor With {@link RpcEnhancementReporterProperties} . * * @param reportProperties instance of {@link RpcEnhancementReporterProperties}. */ - protected AbstractPolarisReporterAdapter(RpcEnhancementReporterProperties reportProperties) { + protected AbstractPolarisReporterAdapter(RpcEnhancementReporterProperties reportProperties, SDKContext context) { this.reportProperties = reportProperties; + this.context = context; + } + + /** + * createServiceCallResult. + * @param calleeServiceName will pick up url host when null + * @param calleeHost will pick up url host when null + * @param calleePort will pick up url port when null + * @param uri request url + * @param requestHeaders request header + * @param responseHeaders response header + * @param statusCode response status + * @param delay delay + * @param exception exception + * @return ServiceCallResult + */ + public ServiceCallResult createServiceCallResult( + @Nullable String calleeServiceName, @Nullable String calleeHost, @Nullable Integer calleePort, + URI uri, HttpHeaders requestHeaders, @Nullable HttpHeaders responseHeaders, + @Nullable Integer statusCode, long delay, @Nullable Throwable exception) { + + ServiceCallResult resultRequest = new ServiceCallResult(); + resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE); + resultRequest.setService(StringUtils.isBlank(calleeServiceName) ? uri.getHost() : calleeServiceName); + resultRequest.setMethod(uri.getPath()); + resultRequest.setRetCode(statusCode == null ? -1 : statusCode); + resultRequest.setDelay(delay); + resultRequest.setCallerService(new ServiceKey(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE)); + resultRequest.setCallerIp(this.context.getConfig().getGlobal().getAPI().getBindIP()); + resultRequest.setHost(StringUtils.isBlank(calleeHost) ? uri.getHost() : calleeHost); + resultRequest.setPort(calleePort == null ? getPort(uri) : calleePort); + resultRequest.setLabels(getLabels(requestHeaders)); + resultRequest.setRetStatus(getRetStatusFromRequest(responseHeaders, getDefaultRetStatus(statusCode, exception))); + resultRequest.setRuleName(getActiveRuleNameFromRequest(responseHeaders)); + return resultRequest; } + /** + * createInstanceResourceStat. + * @param calleeServiceName will pick up url host when null + * @param calleeHost will pick up url host when null + * @param calleePort will pick up url port when null + * @param uri request url + * @param statusCode response status + * @param delay delay + * @param exception exception + * @return ResourceStat + */ + public ResourceStat createInstanceResourceStat( + @Nullable String calleeServiceName, @Nullable String calleeHost, @Nullable Integer calleePort, + URI uri, @Nullable Integer statusCode, long delay, @Nullable Throwable exception) { + ServiceKey calleeServiceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE, StringUtils.isBlank(calleeServiceName) ? uri.getHost() : calleeServiceName); + ServiceKey callerServiceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE); + Resource resource = new InstanceResource( + calleeServiceKey, + StringUtils.isBlank(calleeHost) ? uri.getHost() : calleeHost, + calleePort == null ? getPort(uri) : calleePort, + callerServiceKey + ); + return new ResourceStat(resource, statusCode == null ? -1 : statusCode, delay, getDefaultRetStatus(statusCode, exception)); + } + + /** * Convert items to List. * @@ -116,4 +199,68 @@ public abstract class AbstractPolarisReporterAdapter { // DEFAULT RETURN FALSE. return false; } + + protected RetStatus getRetStatusFromRequest(HttpHeaders headers, RetStatus defaultVal) { + if (headers != null && 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 != null && headers.containsKey(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME)) { + Collection<String> values = headers.get(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME); + if (CollectionUtils.isNotEmpty(values)) { + return com.tencent.polaris.api.utils.StringUtils.defaultString(new ArrayList<>(values).get(0)); + } + } + return ""; + } + + private RetStatus getDefaultRetStatus(Integer statusCode, Throwable exception) { + RetStatus retStatus = RetStatus.RetSuccess; + if (exception != null) { + retStatus = RetStatus.RetFail; + if (exception instanceof SocketTimeoutException) { + retStatus = RetStatus.RetTimeout; + } + } + else if (statusCode == null || apply(HttpStatus.resolve(statusCode))) { + retStatus = RetStatus.RetFail; + } + return retStatus; + } + + private int getPort(URI uri) { + // -1 means access directly by url, and use http default port number 80 + return uri.getPort() == -1 ? 80 : uri.getPort(); + } + + private String getLabels(HttpHeaders headers) { + if (headers != null) { + Collection<String> labels = 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) { + LOG.error("unsupported charset exception " + UTF_8, e); + } + return RequestLabelUtils.convertLabel(label); + } + } + return null; + } + + } 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..582292807 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 @@ -20,17 +20,26 @@ package com.tencent.cloud.rpc.enhancement.config; import java.util.Collections; import java.util.List; +import com.netflix.zuul.ZuulFilter; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; -import com.tencent.cloud.rpc.enhancement.feign.DefaultEnhancedFeignPluginRunner; import com.tencent.cloud.rpc.enhancement.feign.EnhancedFeignBeanPostProcessor; -import com.tencent.cloud.rpc.enhancement.feign.EnhancedFeignPluginRunner; -import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin; -import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.ExceptionPolarisReporter; -import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.SuccessPolarisReporter; +import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; +import com.tencent.cloud.rpc.enhancement.plugin.reporter.ExceptionPolarisReporter; +import com.tencent.cloud.rpc.enhancement.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.resttemplate.EnhancedRestTemplateInterceptor; +import com.tencent.cloud.rpc.enhancement.resttemplate.RibbonLoadBalancerClientAspect; +import com.tencent.cloud.rpc.enhancement.scg.EnhancedGatewayGlobalFilter; +import com.tencent.cloud.rpc.enhancement.webclient.EnhancedWebClientReporter; +import com.tencent.cloud.rpc.enhancement.webclient.PolarisLoadBalancerClientRequestTransformer; +import com.tencent.cloud.rpc.enhancement.zuul.EnhancedErrorZuulFilter; +import com.tencent.cloud.rpc.enhancement.zuul.EnhancedPostZuulFilter; +import com.tencent.cloud.rpc.enhancement.zuul.EnhancedPreZuulFilter; 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; @@ -46,7 +55,9 @@ 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.core.env.Environment; import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; /** * Auto Configuration for Polaris {@link feign.Feign} OR {@link RestTemplate} which can automatically bring in the call @@ -61,6 +72,26 @@ import org.springframework.web.client.RestTemplate; @AutoConfigureAfter(PolarisContextAutoConfiguration.class) public class RpcEnhancementAutoConfiguration { + @Bean + public EnhancedPluginRunner enhancedFeignPluginRunner( + @Autowired(required = false) List<EnhancedPlugin> enhancedPlugins) { + return new DefaultEnhancedPluginRunner(enhancedPlugins); + } + + @Bean + public SuccessPolarisReporter successPolarisReporter(RpcEnhancementReporterProperties properties, + SDKContext context, + ConsumerAPI consumerAPI) { + return new SuccessPolarisReporter(properties, context, consumerAPI); + } + + @Bean + public ExceptionPolarisReporter exceptionPolarisReporter(RpcEnhancementReporterProperties properties, + SDKContext context, + ConsumerAPI consumerAPI) { + return new ExceptionPolarisReporter(properties, context, consumerAPI); + } + /** * Configuration for Polaris {@link feign.Feign} which can automatically bring in the call * results for reporting. @@ -74,31 +105,10 @@ public class RpcEnhancementAutoConfiguration { protected static class PolarisFeignClientAutoConfiguration { @Bean - public EnhancedFeignPluginRunner enhancedFeignPluginRunner( - @Autowired(required = false) List<EnhancedFeignPlugin> enhancedFeignPlugins) { - return new DefaultEnhancedFeignPluginRunner(enhancedFeignPlugins); - } - - @Bean - public EnhancedFeignBeanPostProcessor polarisFeignBeanPostProcessor(@Lazy EnhancedFeignPluginRunner pluginRunner) { + public EnhancedFeignBeanPostProcessor polarisFeignBeanPostProcessor(@Lazy EnhancedPluginRunner pluginRunner) { return new EnhancedFeignBeanPostProcessor(pluginRunner); } - @Configuration - static class PolarisReporterConfig { - - @Bean - public SuccessPolarisReporter successPolarisReporter(RpcEnhancementReporterProperties properties, - @Autowired(required = false) ConsumerAPI consumerAPI) { - return new SuccessPolarisReporter(properties, consumerAPI); - } - - @Bean - public ExceptionPolarisReporter exceptionPolarisReporter(RpcEnhancementReporterProperties properties, - @Autowired(required = false) ConsumerAPI consumerAPI) { - return new ExceptionPolarisReporter(properties, consumerAPI); - } - } } /** @@ -116,16 +126,15 @@ public class RpcEnhancementAutoConfiguration { private List<RestTemplate> restTemplates = Collections.emptyList(); @Bean - public EnhancedRestTemplateReporter enhancedRestTemplateReporter( - RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { - return new EnhancedRestTemplateReporter(properties, consumerAPI); + public EnhancedRestTemplateInterceptor enhancedPolarisRestTemplateReporter(@Lazy EnhancedPluginRunner pluginRunner) { + return new EnhancedRestTemplateInterceptor(pluginRunner); } @Bean - public SmartInitializingSingleton setErrorHandlerForRestTemplate(EnhancedRestTemplateReporter reporter) { + public SmartInitializingSingleton setPolarisReporterForRestTemplate(EnhancedRestTemplateInterceptor reporter) { return () -> { for (RestTemplate restTemplate : restTemplates) { - restTemplate.setErrorHandler(reporter); + restTemplate.getInterceptors().add(reporter); } }; } @@ -136,5 +145,84 @@ public class RpcEnhancementAutoConfiguration { public BlockingLoadBalancerClientAspect blockingLoadBalancerClientAspect() { return new BlockingLoadBalancerClientAspect(); } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(name = {"org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient"}) + public RibbonLoadBalancerClientAspect ribbonLoadBalancerClientAspect() { + return new RibbonLoadBalancerClientAspect(); + } + } + + /** + * 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 { + + @Autowired(required = false) + private List<WebClient.Builder> webClientBuilder = Collections.emptyList(); + + @Bean + public EnhancedWebClientReporter exchangeFilterFunction(@Lazy EnhancedPluginRunner pluginRunner) { + return new EnhancedWebClientReporter(pluginRunner); + } + + @Bean + public SmartInitializingSingleton addEnhancedWebClientReporterForWebClient(EnhancedWebClientReporter reporter) { + return () -> webClientBuilder.forEach(webClient -> { + webClient.filter(reporter); + }); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerClientRequestTransformer") + public PolarisLoadBalancerClientRequestTransformer polarisLoadBalancerClientRequestTransformer() { + return new PolarisLoadBalancerClientRequestTransformer(); + } + + } + + /** + * 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.GlobalFilter") + public EnhancedGatewayGlobalFilter enhancedPolarisGatewayReporter(@Lazy EnhancedPluginRunner pluginRunner) { + return new EnhancedGatewayGlobalFilter(pluginRunner); + } + + } + + /** + * Configuration for Polaris {@link ZuulFilter} which can automatically bring in the call + * results for reporting. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "com.netflix.zuul.http.ZuulServlet") + protected static class PolarisCircuitBreakerZuulFilterConfig { + @Bean + public EnhancedPreZuulFilter enhancedZuulPreFilter(@Lazy EnhancedPluginRunner pluginRunner, Environment environment) { + return new EnhancedPreZuulFilter(pluginRunner, environment); + } + + @Bean + public EnhancedPostZuulFilter enhancedZuulPostFilter(@Lazy EnhancedPluginRunner pluginRunner, Environment environment) { + return new EnhancedPostZuulFilter(pluginRunner, environment); + } + + @Bean + public EnhancedErrorZuulFilter enhancedErrorZuulFilter(@Lazy EnhancedPluginRunner pluginRunner, Environment environment) { + return new EnhancedErrorZuulFilter(pluginRunner, environment); + } } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementReporterProperties.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementReporterProperties.java index fc51b31c6..8bfaf9b9e 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementReporterProperties.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementReporterProperties.java @@ -35,7 +35,7 @@ public class RpcEnhancementReporterProperties { /** * Whether report call result to polaris. */ - private boolean enabled; + private boolean enabled = true; /** * Specify the Http status code(s) that needs to be reported as FAILED. diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignBeanPostProcessor.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignBeanPostProcessor.java index b415172ad..3e0fedd0f 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignBeanPostProcessor.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignBeanPostProcessor.java @@ -17,6 +17,7 @@ package com.tencent.cloud.rpc.enhancement.feign; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; import feign.Client; import org.springframework.beans.BeansException; @@ -26,6 +27,7 @@ import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient; +import org.springframework.cloud.openfeign.loadbalancer.RetryableFeignBlockingLoadBalancerClient; import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory; import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient; import org.springframework.lang.NonNull; @@ -37,11 +39,11 @@ import org.springframework.lang.NonNull; */ public class EnhancedFeignBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { - private EnhancedFeignPluginRunner pluginRunner; + private final EnhancedPluginRunner pluginRunner; private BeanFactory factory; - public EnhancedFeignBeanPostProcessor(EnhancedFeignPluginRunner pluginRunner) { + public EnhancedFeignBeanPostProcessor(EnhancedPluginRunner pluginRunner) { this.pluginRunner = pluginRunner; } @@ -59,11 +61,19 @@ public class EnhancedFeignBeanPostProcessor implements BeanPostProcessor, BeanFa factory(), clientFactory()); } - if (bean instanceof FeignBlockingLoadBalancerClient) { - FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) bean; - return new EnhancedFeignBlockingLoadBalancerClient( - createPolarisFeignClient(client.getDelegate()), - factory.getBean(BlockingLoadBalancerClient.class)); + if (bean instanceof RetryableFeignBlockingLoadBalancerClient + || bean instanceof FeignBlockingLoadBalancerClient) { + Client delegate; + if (bean instanceof RetryableFeignBlockingLoadBalancerClient) { + delegate = ((RetryableFeignBlockingLoadBalancerClient) bean).getDelegate(); + } + else { + delegate = ((FeignBlockingLoadBalancerClient) bean).getDelegate(); + } + if (delegate != null) { + return new EnhancedFeignBlockingLoadBalancerClient(createPolarisFeignClient(delegate), + factory.getBean(BlockingLoadBalancerClient.class)); + } } return createPolarisFeignClient((Client) bean); } @@ -81,7 +91,7 @@ public class EnhancedFeignBeanPostProcessor implements BeanPostProcessor, BeanFa } @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException { this.factory = beanFactory; } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClient.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClient.java index 4bdf14eff..e191edfde 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClient.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClient.java @@ -18,17 +18,26 @@ package com.tencent.cloud.rpc.enhancement.feign; import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; -import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; import feign.Client; import feign.Request; import feign.Request.Options; import feign.Response; -import static com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType.EXCEPTION; -import static com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType.FINALLY; -import static com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType.POST; -import static com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType.PRE; +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; + +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.EXCEPTION; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.FINALLY; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.POST; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.PRE; import static feign.Util.checkNotNull; /** @@ -40,38 +49,64 @@ public class EnhancedFeignClient implements Client { private final Client delegate; - private EnhancedFeignPluginRunner pluginRunner; + private final EnhancedPluginRunner pluginRunner; - public EnhancedFeignClient(Client target, EnhancedFeignPluginRunner pluginRunner) { + public EnhancedFeignClient(Client target, EnhancedPluginRunner pluginRunner) { this.delegate = checkNotNull(target, "target"); this.pluginRunner = pluginRunner; } @Override public Response execute(Request request, Options options) throws IOException { - EnhancedFeignContext enhancedFeignContext = new EnhancedFeignContext(); - enhancedFeignContext.setRequest(request); - enhancedFeignContext.setOptions(options); + EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext(); + + HttpHeaders requestHeaders = new HttpHeaders(); + request.headers().forEach((s, strings) -> requestHeaders.addAll(s, new ArrayList<>(strings))); + URI url = URI.create(request.url()); + + EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder() + .httpHeaders(requestHeaders) + .httpMethod(HttpMethod.resolve(request.httpMethod().name())) + .url(url) + .build(); + enhancedPluginContext.setRequest(enhancedRequestContext); - // Run pre enhanced feign plugins. - pluginRunner.run(PRE, enhancedFeignContext); + // Run pre enhanced plugins. + pluginRunner.run(PRE, enhancedPluginContext); + long startMillis = System.currentTimeMillis(); try { Response response = delegate.execute(request, options); - enhancedFeignContext.setResponse(response); + enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); + + HttpHeaders responseHeaders = new HttpHeaders(); + response.headers().forEach((s, strings) -> responseHeaders.addAll(s, new ArrayList<>(strings))); + + EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder() + .httpStatus(response.status()) + .httpHeaders(responseHeaders) + .build(); + enhancedPluginContext.setResponse(enhancedResponseContext); + + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); + serviceInstance.setServiceId(request.requestTemplate().feignTarget().name()); + serviceInstance.setHost(url.getHost()); + serviceInstance.setPort(url.getPort()); + enhancedPluginContext.setServiceInstance(serviceInstance); - // Run post enhanced feign plugins. - pluginRunner.run(POST, enhancedFeignContext); + // Run post enhanced plugins. + pluginRunner.run(POST, enhancedPluginContext); return response; } catch (IOException origin) { - enhancedFeignContext.setException(origin); + enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); + enhancedPluginContext.setThrowable(origin); // Run exception enhanced feign plugins. - pluginRunner.run(EXCEPTION, enhancedFeignContext); + pluginRunner.run(EXCEPTION, enhancedPluginContext); throw origin; } finally { - // Run finally enhanced feign plugins. - pluginRunner.run(FINALLY, enhancedFeignContext); + // Run finally enhanced plugins. + pluginRunner.run(FINALLY, enhancedPluginContext); } } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/EnhancedFeignContext.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/EnhancedFeignContext.java deleted file mode 100644 index e6f3be612..000000000 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/EnhancedFeignContext.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.feign.plugin; - -import feign.Request; -import feign.Response; - -/** - * Context used by EnhancedFeignPlugin. - * - * @author Haotian Zhang - */ -public class EnhancedFeignContext { - - private Request request; - - private Request.Options options; - - private Response response; - - private Exception exception; - - public Request getRequest() { - return request; - } - - public void setRequest(Request request) { - this.request = request; - } - - public Request.Options getOptions() { - return options; - } - - public void setOptions(Request.Options options) { - this.options = options; - } - - public Response getResponse() { - return response; - } - - public void setResponse(Response response) { - this.response = response; - } - - public Exception getException() { - return exception; - } - - public void setException(Exception exception) { - this.exception = exception; - } -} 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 deleted file mode 100644 index 0e1b2c87a..000000000 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporter.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.feign.plugin.reporter; - -import java.net.SocketTimeoutException; - -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; -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 feign.Request; -import feign.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.core.Ordered; - -/** - * Polaris reporter when feign call fails. - * - * @author Haotian Zhang - */ -public class ExceptionPolarisReporter implements EnhancedFeignPlugin { - - private static final Logger LOG = LoggerFactory.getLogger(ExceptionPolarisReporter.class); - private final RpcEnhancementReporterProperties reporterProperties; - - private final ConsumerAPI consumerAPI; - - public ExceptionPolarisReporter(RpcEnhancementReporterProperties reporterProperties, - ConsumerAPI consumerAPI) { - this.reporterProperties = reporterProperties; - this.consumerAPI = consumerAPI; - } - - @Override - public String getName() { - return ExceptionPolarisReporter.class.getName(); - } - - @Override - public EnhancedFeignPluginType getType() { - return EnhancedFeignPluginType.EXCEPTION; - } - - @Override - public void run(EnhancedFeignContext context) { - if (!reporterProperties.isEnabled()) { - return; - } - - if (consumerAPI != null) { - Request request = context.getRequest(); - Response response = context.getResponse(); - Exception exception = context.getException(); - RetStatus retStatus = RetStatus.RetFail; - if (exception instanceof SocketTimeoutException) { - retStatus = RetStatus.RetTimeout; - } - LOG.debug("Will report result of {}. Request=[{}]. Response=[{}].", retStatus.name(), request, response); - ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(request, retStatus); - consumerAPI.updateServiceCallResult(resultRequest); - // update result without method for service circuit break. - resultRequest.setMethod(""); - consumerAPI.updateServiceCallResult(resultRequest); - } - } - - @Override - public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) { - Request request = context.getRequest(); - Response response = context.getResponse(); - LOG.error("ExceptionPolarisReporter runs failed. Request=[{}]. Response=[{}].", request, response, throwable); - } - - @Override - public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE + 1; - } -} 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 deleted file mode 100644 index b1d15f532..000000000 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtils.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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.feign.plugin.reporter; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLDecoder; -import java.util.Collection; - -import com.tencent.cloud.common.constant.RouterConstant; -import com.tencent.cloud.common.metadata.MetadataContext; -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 feign.Request; -import feign.RequestTemplate; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; - -/** - * Util for polaris reporter. - * - * @author Haotian Zhang - */ -public final class ReporterUtils { - - private static final Logger LOGGER = LoggerFactory.getLogger(ReporterUtils.class); - - private ReporterUtils() { - } - - public static ServiceCallResult createServiceCallResult(final Request request, RetStatus retStatus) { - ServiceCallResult resultRequest = new ServiceCallResult(); - - resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE); - RequestTemplate requestTemplate = request.requestTemplate(); - String serviceName = requestTemplate.feignTarget().name(); - Collection<String> labels = requestTemplate.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); - } - resultRequest.setLabels(convertLabel(label)); - } - resultRequest.setService(serviceName); - URI uri = URI.create(request.url()); - resultRequest.setMethod(uri.getPath()); - resultRequest.setRetStatus(retStatus); - String sourceNamespace = MetadataContext.LOCAL_NAMESPACE; - String sourceService = MetadataContext.LOCAL_SERVICE; - if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) { - resultRequest.setCallerService(new ServiceKey(sourceNamespace, sourceService)); - } - 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()); - return resultRequest; - } - - private static String convertLabel(String label) { - label = label.replaceAll("\"|\\{|\\}", "") - .replaceAll(",", "|"); - return label; - } -} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java deleted file mode 100644 index 2fefd6dcf..000000000 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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.feign.plugin.reporter; - -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; -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 feign.Request; -import feign.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.core.Ordered; -import org.springframework.http.HttpStatus; - -/** - * Polaris reporter when feign call is successful. - * - * @author Haotian Zhang - */ -public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter implements EnhancedFeignPlugin { - - private static final Logger LOG = LoggerFactory.getLogger(SuccessPolarisReporter.class); - - private final ConsumerAPI consumerAPI; - - public SuccessPolarisReporter(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { - super(properties); - this.consumerAPI = consumerAPI; - } - - @Override - public String getName() { - return SuccessPolarisReporter.class.getName(); - } - - @Override - public EnhancedFeignPluginType getType() { - return EnhancedFeignPluginType.POST; - } - - @Override - public void run(EnhancedFeignContext context) { - if (!reportProperties.isEnabled()) { - return; - } - - if (consumerAPI != null) { - Request request = context.getRequest(); - Response response = context.getResponse(); - RetStatus retStatus = RetStatus.RetSuccess; - if (apply(HttpStatus.resolve(response.status()))) { - retStatus = RetStatus.RetFail; - } - LOG.debug("Will report result of {}. Request=[{}]. Response=[{}].", retStatus.name(), request, response); - ServiceCallResult resultRequest = ReporterUtils.createServiceCallResult(request, retStatus); - consumerAPI.updateServiceCallResult(resultRequest); - // update result without method for service circuit break. - resultRequest.setMethod(""); - consumerAPI.updateServiceCallResult(resultRequest); - } - } - - @Override - public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) { - Request request = context.getRequest(); - Response response = context.getResponse(); - LOG.error("SuccessPolarisReporter runs failed. Request=[{}]. Response=[{}].", request, response, throwable); - } - - @Override - public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE + 1; - } -} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/DefaultEnhancedFeignPluginRunner.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunner.java similarity index 61% rename from spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/DefaultEnhancedFeignPluginRunner.java rename to spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunner.java index dbaf7d580..ca34b2af1 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/DefaultEnhancedFeignPluginRunner.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunner.java @@ -16,16 +16,13 @@ * */ -package com.tencent.cloud.rpc.enhancement.feign; +package com.tencent.cloud.rpc.enhancement.plugin; import java.util.Comparator; import java.util.List; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; -import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; -import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin; -import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; import org.springframework.util.CollectionUtils; @@ -34,14 +31,14 @@ import org.springframework.util.CollectionUtils; * * @author Derek Yi 2022-08-16 */ -public class DefaultEnhancedFeignPluginRunner implements EnhancedFeignPluginRunner { +public class DefaultEnhancedPluginRunner implements EnhancedPluginRunner { - private final Multimap<String, EnhancedFeignPlugin> pluginMap = ArrayListMultimap.create(); + private final Multimap<String, EnhancedPlugin> pluginMap = ArrayListMultimap.create(); - public DefaultEnhancedFeignPluginRunner(List<EnhancedFeignPlugin> enhancedFeignPlugins) { - if (!CollectionUtils.isEmpty(enhancedFeignPlugins)) { - enhancedFeignPlugins.stream() - .sorted(Comparator.comparing(EnhancedFeignPlugin::getOrder)) + public DefaultEnhancedPluginRunner(List<EnhancedPlugin> enhancedPlugins) { + if (!CollectionUtils.isEmpty(enhancedPlugins)) { + enhancedPlugins.stream() + .sorted(Comparator.comparing(EnhancedPlugin::getOrder)) .forEach(plugin -> pluginMap.put(plugin.getType().name(), plugin)); } } @@ -53,8 +50,8 @@ public class DefaultEnhancedFeignPluginRunner implements EnhancedFeignPluginRunn * @param context context in enhanced feign client. */ @Override - public void run(EnhancedFeignPluginType pluginType, EnhancedFeignContext context) { - for (EnhancedFeignPlugin plugin : pluginMap.get(pluginType.name())) { + public void run(EnhancedPluginType pluginType, EnhancedPluginContext context) { + for (EnhancedPlugin plugin : pluginMap.get(pluginType.name())) { try { plugin.run(context); } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/EnhancedFeignPlugin.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPlugin.java similarity index 75% rename from spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/EnhancedFeignPlugin.java rename to spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPlugin.java index 1ecc3d355..39bce26a3 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/EnhancedFeignPlugin.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPlugin.java @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.rpc.enhancement.feign.plugin; +package com.tencent.cloud.rpc.enhancement.plugin; import org.springframework.core.Ordered; @@ -24,7 +24,7 @@ import org.springframework.core.Ordered; * * @author Haotian Zhang */ -public interface EnhancedFeignPlugin extends Ordered { +public interface EnhancedPlugin extends Ordered { /** * Get name of plugin. @@ -38,9 +38,9 @@ public interface EnhancedFeignPlugin extends Ordered { /** * Get type of plugin. * - * @return {@link EnhancedFeignPluginType} + * @return {@link EnhancedPluginType} */ - EnhancedFeignPluginType getType(); + EnhancedPluginType getType(); /** * Run the plugin. @@ -48,15 +48,15 @@ public interface EnhancedFeignPlugin extends Ordered { * @param context context in enhanced feign client. * @throws Throwable throwable thrown from run method. */ - void run(EnhancedFeignContext context) throws Throwable; + void run(EnhancedPluginContext context) throws Throwable; /** - * Handler throwable from {@link EnhancedFeignPlugin#run(EnhancedFeignContext)}. + * Handler throwable from {@link EnhancedPlugin#run(EnhancedPluginContext)}. * * @param context context in enhanced feign client. * @param throwable throwable thrown from run method. */ - default void handlerThrowable(EnhancedFeignContext context, Throwable throwable) { + default void handlerThrowable(EnhancedPluginContext context, Throwable throwable) { } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContext.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContext.java new file mode 100644 index 000000000..fa4e891d0 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContext.java @@ -0,0 +1,90 @@ +/* + * 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.plugin; + + +import org.springframework.cloud.client.ServiceInstance; + +/** + * Context used by EnhancedPlugin. + * + * @author Haotian Zhang + */ +public class EnhancedPluginContext { + + private EnhancedRequestContext request; + + private EnhancedResponseContext response; + + private Throwable throwable; + + private long delay; + + private ServiceInstance serviceInstance; + + public EnhancedRequestContext getRequest() { + return request; + } + + public void setRequest(EnhancedRequestContext request) { + this.request = request; + } + + public EnhancedResponseContext getResponse() { + return response; + } + + public void setResponse(EnhancedResponseContext response) { + this.response = response; + } + + public Throwable getThrowable() { + return throwable; + } + + public void setThrowable(Throwable throwable) { + this.throwable = throwable; + } + + public long getDelay() { + return delay; + } + + public void setDelay(long delay) { + this.delay = delay; + } + + public ServiceInstance getServiceInstance() { + return serviceInstance; + } + + public void setServiceInstance(ServiceInstance serviceInstance) { + this.serviceInstance = serviceInstance; + } + + @Override + public String toString() { + return "EnhancedPluginContext{" + + "request=" + request + + ", response=" + response + + ", throwable=" + throwable + + ", delay=" + delay + + ", serviceInstance=" + serviceInstance + + '}'; + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignPluginRunner.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginRunner.java similarity index 73% rename from spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignPluginRunner.java rename to spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginRunner.java index b821827b2..e15af39e5 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignPluginRunner.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginRunner.java @@ -16,17 +16,14 @@ * */ -package com.tencent.cloud.rpc.enhancement.feign; - -import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; -import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; +package com.tencent.cloud.rpc.enhancement.plugin; /** * Plugin runner. * * @author Derek Yi 2022-08-16 */ -public interface EnhancedFeignPluginRunner { +public interface EnhancedPluginRunner { /** * run the plugin. @@ -34,5 +31,5 @@ public interface EnhancedFeignPluginRunner { * @param pluginType type of plugin * @param context context in enhanced feign client. */ - void run(EnhancedFeignPluginType pluginType, EnhancedFeignContext context); + void run(EnhancedPluginType pluginType, EnhancedPluginContext context); } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/EnhancedFeignPluginType.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginType.java similarity index 88% rename from spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/EnhancedFeignPluginType.java rename to spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginType.java index fef181d83..64dc2ecb9 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/EnhancedFeignPluginType.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginType.java @@ -15,14 +15,14 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.rpc.enhancement.feign.plugin; +package com.tencent.cloud.rpc.enhancement.plugin; /** - * Type of EnhancedFeignPlugin. + * Type of EnhancedPlugin. * * @author Haotian Zhang */ -public enum EnhancedFeignPluginType { +public enum EnhancedPluginType { /** * Pre feign plugin. diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedRequestContext.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedRequestContext.java new file mode 100644 index 000000000..74c9aff63 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedRequestContext.java @@ -0,0 +1,107 @@ +/* + * 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.plugin; + +import java.net.URI; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; + +/** + * EnhancedRequestContext. + * + * @author sean yu + */ +public class EnhancedRequestContext { + + private HttpMethod httpMethod; + + private HttpHeaders httpHeaders; + + private URI url; + + public HttpMethod getHttpMethod() { + return httpMethod; + } + + public void setHttpMethod(HttpMethod httpMethod) { + this.httpMethod = httpMethod; + } + + public HttpHeaders getHttpHeaders() { + return httpHeaders; + } + + public void setHttpHeaders(HttpHeaders httpHeaders) { + this.httpHeaders = httpHeaders; + } + + public URI getUrl() { + return url; + } + + public void setUrl(URI url) { + this.url = url; + } + + public static EnhancedContextRequestBuilder builder() { + return new EnhancedContextRequestBuilder(); + } + + @Override + public String toString() { + return "EnhancedRequestContext{" + + "httpMethod=" + httpMethod + + ", httpHeaders=" + httpHeaders + + ", url=" + url + + '}'; + } + + public static final class EnhancedContextRequestBuilder { + private HttpMethod httpMethod; + private HttpHeaders httpHeaders; + private URI url; + + private EnhancedContextRequestBuilder() { + } + + public EnhancedContextRequestBuilder httpMethod(HttpMethod httpMethod) { + this.httpMethod = httpMethod; + return this; + } + + public EnhancedContextRequestBuilder httpHeaders(HttpHeaders httpHeaders) { + this.httpHeaders = httpHeaders; + return this; + } + + public EnhancedContextRequestBuilder url(URI url) { + this.url = url; + return this; + } + + public EnhancedRequestContext build() { + EnhancedRequestContext enhancedRequestContext = new EnhancedRequestContext(); + enhancedRequestContext.httpMethod = this.httpMethod; + enhancedRequestContext.url = this.url; + enhancedRequestContext.httpHeaders = this.httpHeaders; + return enhancedRequestContext; + } + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedResponseContext.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedResponseContext.java new file mode 100644 index 000000000..c8773776a --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedResponseContext.java @@ -0,0 +1,86 @@ +/* + * 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.plugin; + +import org.springframework.http.HttpHeaders; + +/** + * EnhancedResponseContext. + * + * @author sean yu + */ +public class EnhancedResponseContext { + + private Integer httpStatus; + + private HttpHeaders httpHeaders; + + public Integer getHttpStatus() { + return httpStatus; + } + + public void setHttpStatus(Integer httpStatus) { + this.httpStatus = httpStatus; + } + + public HttpHeaders getHttpHeaders() { + return httpHeaders; + } + + public void setHttpHeaders(HttpHeaders httpHeaders) { + this.httpHeaders = httpHeaders; + } + + public static EnhancedContextResponseBuilder builder() { + return new EnhancedContextResponseBuilder(); + } + + @Override + public String toString() { + return "EnhancedResponseContext{" + + "httpStatus=" + httpStatus + + ", httpHeaders=" + httpHeaders + + '}'; + } + + public static final class EnhancedContextResponseBuilder { + private Integer httpStatus; + private HttpHeaders httpHeaders; + + private EnhancedContextResponseBuilder() { + } + + public EnhancedContextResponseBuilder httpStatus(Integer httpStatus) { + this.httpStatus = httpStatus; + return this; + } + + public EnhancedContextResponseBuilder httpHeaders(HttpHeaders httpHeaders) { + this.httpHeaders = httpHeaders; + return this; + } + + public EnhancedResponseContext build() { + EnhancedResponseContext enhancedResponseContext = new EnhancedResponseContext(); + enhancedResponseContext.setHttpStatus(httpStatus); + enhancedResponseContext.setHttpHeaders(httpHeaders); + return enhancedResponseContext; + } + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/reporter/ExceptionPolarisReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/reporter/ExceptionPolarisReporter.java new file mode 100644 index 000000000..76c4250b3 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/reporter/ExceptionPolarisReporter.java @@ -0,0 +1,106 @@ +/* + * 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.plugin.reporter; + + +import java.util.Optional; + +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.core.Ordered; + +/** + * Polaris reporter when feign call fails. + * + * @author Haotian Zhang + */ +public class ExceptionPolarisReporter extends AbstractPolarisReporterAdapter implements EnhancedPlugin { + + private static final Logger LOG = LoggerFactory.getLogger(ExceptionPolarisReporter.class); + + private final ConsumerAPI consumerAPI; + + public ExceptionPolarisReporter(RpcEnhancementReporterProperties reporterProperties, + SDKContext context, + ConsumerAPI consumerAPI) { + super(reporterProperties, context); + this.consumerAPI = consumerAPI; + } + + @Override + public String getName() { + return ExceptionPolarisReporter.class.getName(); + } + + @Override + public EnhancedPluginType getType() { + return EnhancedPluginType.EXCEPTION; + } + + @Override + public void run(EnhancedPluginContext context) { + if (!super.reportProperties.isEnabled()) { + return; + } + + EnhancedRequestContext request = context.getRequest(); + ServiceInstance serviceInstance = Optional.ofNullable(context.getServiceInstance()).orElse(new DefaultServiceInstance()); + + ServiceCallResult resultRequest = createServiceCallResult( + serviceInstance.getServiceId(), + serviceInstance.getHost(), + serviceInstance.getPort(), + request.getUrl(), + request.getHttpHeaders(), + null, + null, + context.getDelay(), + context.getThrowable() + ); + + LOG.debug("Will report ServiceCallResult of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.", + resultRequest.getRetStatus().name(), request.getHttpMethod().name(), request.getUrl().getPath(), context.getThrowable().getMessage(), context.getDelay()); + + consumerAPI.updateServiceCallResult(resultRequest); + + } + + @Override + public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) { + LOG.error("ExceptionPolarisReporter runs failed. context=[{}].", + context, throwable); + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE + 1; + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/reporter/SuccessPolarisReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/reporter/SuccessPolarisReporter.java new file mode 100644 index 000000000..4b35396da --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/reporter/SuccessPolarisReporter.java @@ -0,0 +1,106 @@ +/* + * 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.plugin.reporter; + +import java.util.Optional; + +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.core.Ordered; + +/** + * Polaris reporter when feign call is successful. + * + * @author Haotian Zhang + */ +public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter implements EnhancedPlugin { + + private static final Logger LOG = LoggerFactory.getLogger(SuccessPolarisReporter.class); + + private final ConsumerAPI consumerAPI; + + public SuccessPolarisReporter(RpcEnhancementReporterProperties properties, + SDKContext context, + ConsumerAPI consumerAPI) { + super(properties, context); + this.consumerAPI = consumerAPI; + } + + @Override + public String getName() { + return SuccessPolarisReporter.class.getName(); + } + + @Override + public EnhancedPluginType getType() { + return EnhancedPluginType.POST; + } + + @Override + public void run(EnhancedPluginContext context) { + if (!super.reportProperties.isEnabled()) { + return; + } + + EnhancedRequestContext request = context.getRequest(); + EnhancedResponseContext response = context.getResponse(); + ServiceInstance serviceInstance = Optional.ofNullable(context.getServiceInstance()).orElse(new DefaultServiceInstance()); + + ServiceCallResult resultRequest = createServiceCallResult( + serviceInstance.getServiceId(), + serviceInstance.getHost(), + serviceInstance.getPort(), + request.getUrl(), + request.getHttpHeaders(), + response.getHttpHeaders(), + response.getHttpStatus(), + context.getDelay(), + null + ); + + LOG.debug("Will report ServiceCallResult of {}. Request=[{} {}]. Response=[{}]. Delay=[{}]ms.", + resultRequest.getRetStatus().name(), request.getHttpMethod().name(), request.getUrl().getPath(), response.getHttpStatus(), context.getDelay()); + + consumerAPI.updateServiceCallResult(resultRequest); + + } + + @Override + public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) { + LOG.error("SuccessPolarisReporter runs failed. context=[{}].", + context, throwable); + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE + 1; + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateInterceptor.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateInterceptor.java new file mode 100644 index 000000000..e67e241f2 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateInterceptor.java @@ -0,0 +1,105 @@ +/* + * 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.resttemplate; + +import java.io.IOException; +import java.util.Map; + +import com.tencent.cloud.common.constant.HeaderConstant; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.EXCEPTION; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.FINALLY; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.POST; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.PRE; + +/** + * EnhancedRestTemplateInterceptor. + * + * @author sean yu + */ +public class EnhancedRestTemplateInterceptor implements ClientHttpRequestInterceptor { + + private final EnhancedPluginRunner pluginRunner; + + public EnhancedRestTemplateInterceptor(EnhancedPluginRunner pluginRunner) { + this.pluginRunner = pluginRunner; + } + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + + EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext(); + + EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder() + .httpHeaders(request.getHeaders()) + .httpMethod(request.getMethod()) + .url(request.getURI()) + .build(); + enhancedPluginContext.setRequest(enhancedRequestContext); + + // Run pre enhanced plugins. + pluginRunner.run(PRE, enhancedPluginContext); + long startMillis = System.currentTimeMillis(); + try { + ClientHttpResponse response = execution.execute(request, body); + enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); + + EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder() + .httpStatus(response.getRawStatusCode()) + .httpHeaders(response.getHeaders()) + .build(); + enhancedPluginContext.setResponse(enhancedResponseContext); + + Map<String, String> loadBalancerContext = MetadataContextHolder.get().getLoadbalancerMetadata(); + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); + serviceInstance.setServiceId(request.getURI().getHost()); + serviceInstance.setHost(loadBalancerContext.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST)); + if (loadBalancerContext.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT) != null) { + serviceInstance.setPort(Integer.parseInt(loadBalancerContext.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT))); + } + enhancedPluginContext.setServiceInstance(serviceInstance); + + // Run post enhanced plugins. + pluginRunner.run(POST, enhancedPluginContext); + return response; + } + catch (IOException e) { + enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); + enhancedPluginContext.setThrowable(e); + // Run exception enhanced plugins. + pluginRunner.run(EXCEPTION, enhancedPluginContext); + throw e; + } + finally { + // Run finally enhanced plugins. + pluginRunner.run(FINALLY, enhancedPluginContext); + } + } + +} 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 deleted file mode 100644 index df1ec9907..000000000 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * 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.resttemplate; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLDecoder; -import java.util.List; -import java.util.Map; - -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.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 org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.http.HttpMethod; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.lang.NonNull; -import org.springframework.web.client.DefaultResponseErrorHandler; -import org.springframework.web.client.ResponseErrorHandler; - -import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; - -/** - * Extend ResponseErrorHandler to get request information. - * - * @author wh 2022/6/21 - */ -public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter implements ResponseErrorHandler, ApplicationContextAware { - - static final String HEADER_HAS_ERROR = "X-SCT-Has-Error"; - private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedRestTemplateReporter.class); - private final ConsumerAPI consumerAPI; - private ResponseErrorHandler delegateHandler; - - public EnhancedRestTemplateReporter(RpcEnhancementReporterProperties properties, ConsumerAPI consumerAPI) { - super(properties); - this.consumerAPI = consumerAPI; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - String[] handlerBeanNames = applicationContext.getBeanNamesForType(ResponseErrorHandler.class); - if (handlerBeanNames.length == 1) { - if (this.delegateHandler == null) { - this.delegateHandler = new DefaultResponseErrorHandler(); - } - return; - } - - // inject user custom ResponseErrorHandler - for (String beanName : handlerBeanNames) { - // ignore self - if (StringUtils.equalsIgnoreCase("enhancedRestTemplateReporter", beanName)) { - continue; - } - this.delegateHandler = (ResponseErrorHandler) applicationContext.getBean(beanName); - } - } - - @Override - public boolean hasError(@NonNull ClientHttpResponse response) throws IOException { - if (delegateHandler != null) { - // Preserve the delegated handler result - boolean hasError = delegateHandler.hasError(response); - response.getHeaders().add(HEADER_HAS_ERROR, String.valueOf(hasError)); - } - return true; - } - - @Override - public void handleError(@NonNull ClientHttpResponse response) throws IOException { - if (realHasError(response)) { - delegateHandler.handleError(response); - } - - clear(response); - } - - @Override - public void handleError(@NonNull URI url, @NonNull HttpMethod method, @NonNull ClientHttpResponse response) throws IOException { - // report result to polaris - if (reportProperties.isEnabled()) { - reportResult(url, response); - } - - // invoke delegate handler - invokeDelegateHandler(url, method, response); - } - - private void reportResult(URI url, ClientHttpResponse response) { - ServiceCallResult resultRequest = createServiceCallResult(url); - try { - Map<String, String> loadBalancerContext = MetadataContextHolder.get().getLoadbalancerMetadata(); - - String targetHost = loadBalancerContext.get("host"); - String targetPort = loadBalancerContext.get("port"); - - if (StringUtils.isBlank(targetHost) || StringUtils.isBlank(targetPort)) { - LOGGER.warn("Can not get target host or port from metadata context. host = {}, port = {}", targetHost, targetPort); - return; - } - - resultRequest.setHost(targetHost); - resultRequest.setPort(Integer.parseInt(targetPort)); - - // checking response http status code - if (apply(response.getStatusCode())) { - resultRequest.setRetStatus(RetStatus.RetFail); - } - - List<String> labels = response.getHeaders().get(RouterConstant.ROUTER_LABEL_HEADER); - if (CollectionUtils.isNotEmpty(labels)) { - String label = labels.get(0); - try { - label = URLDecoder.decode(label, UTF_8); - } - catch (UnsupportedEncodingException e) { - LOGGER.error("unsupported charset exception " + UTF_8, e); - } - resultRequest.setLabels(convertLabel(label)); - } - - // processing report with consumerAPI . - LOGGER.debug("Will report result of {}. URL=[{}]. Response=[{}].", resultRequest.getRetStatus().name(), - url, response); - consumerAPI.updateServiceCallResult(resultRequest); - // update result without method for service circuit break. - resultRequest.setMethod(""); - consumerAPI.updateServiceCallResult(resultRequest); - } - catch (Exception e) { - LOGGER.error("RestTemplate response reporter execute failed of {} url {}", response, url, e); - } - } - - 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); - } - - clear(response); - } - - private Boolean realHasError(ClientHttpResponse response) { - if (delegateHandler == null) { - return false; - } - - String hasErrorHeader = response.getHeaders().getFirst(HEADER_HAS_ERROR); - if (StringUtils.isBlank(hasErrorHeader)) { - return false; - } - - return Boolean.parseBoolean(hasErrorHeader); - } - - private void clear(ClientHttpResponse response) { - if (!response.getHeaders().containsKey(HEADER_HAS_ERROR)) { - return; - } - response.getHeaders().remove(HEADER_HAS_ERROR); - } - - private ServiceCallResult createServiceCallResult(URI uri) { - ServiceCallResult resultRequest = new ServiceCallResult(); - String serviceName = uri.getHost(); - resultRequest.setService(serviceName); - resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE); - resultRequest.setMethod(uri.getPath()); - resultRequest.setRetStatus(RetStatus.RetSuccess); - String sourceNamespace = MetadataContext.LOCAL_NAMESPACE; - String sourceService = MetadataContext.LOCAL_SERVICE; - if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) { - resultRequest.setCallerService(new ServiceKey(sourceNamespace, sourceService)); - } - return resultRequest; - } - - protected ResponseErrorHandler getDelegateHandler() { - return this.delegateHandler; - } - - protected void setDelegateHandler(ResponseErrorHandler delegateHandler) { - this.delegateHandler = delegateHandler; - } -} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/LoadBalancerClientAspectUtils.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/LoadBalancerClientAspectUtils.java index 0eb2dfd7f..04ffac0cf 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/LoadBalancerClientAspectUtils.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/LoadBalancerClientAspectUtils.java @@ -17,7 +17,7 @@ package com.tencent.cloud.rpc.enhancement.resttemplate; -import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.constant.HeaderConstant; import com.tencent.cloud.common.metadata.MetadataContextHolder; import org.aspectj.lang.ProceedingJoinPoint; @@ -37,9 +37,8 @@ public final class LoadBalancerClientAspectUtils { Object server = joinPoint.getArgs()[0]; if (server instanceof ServiceInstance) { ServiceInstance instance = (ServiceInstance) server; - MetadataContextHolder.get().putContext(MetadataContext.FRAGMENT_LOAD_BALANCER, "host", instance.getHost()); - MetadataContextHolder.get() - .putContext(MetadataContext.FRAGMENT_LOAD_BALANCER, "port", String.valueOf(instance.getPort())); + MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST, instance.getHost()); + MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT, String.valueOf(instance.getPort())); } } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilter.java new file mode 100644 index 000000000..6f0a02615 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilter.java @@ -0,0 +1,116 @@ +/* + * 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.URI; + +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.core.Ordered; +import org.springframework.web.server.ServerWebExchange; + +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.EXCEPTION; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.FINALLY; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.POST; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.PRE; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; + +/** + * EnhancedGatewayGlobalFilter. + * + * @author sean yu + */ +public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered { + + private final EnhancedPluginRunner pluginRunner; + + public EnhancedGatewayGlobalFilter(EnhancedPluginRunner pluginRunner) { + this.pluginRunner = pluginRunner; + } + + @Override + public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { + EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext(); + + EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder() + .httpHeaders(exchange.getRequest().getHeaders()) + .httpMethod(exchange.getRequest().getMethod()) + .url(exchange.getRequest().getURI()) + .build(); + enhancedPluginContext.setRequest(enhancedRequestContext); + + // Run pre enhanced plugins. + pluginRunner.run(PRE, enhancedPluginContext); + + long startTime = System.currentTimeMillis(); + return chain.filter(exchange) + .doOnSubscribe(v -> { + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); + Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); + if (route != null) { + serviceInstance.setServiceId(route.getUri().getHost()); + } + URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); + if (uri != null) { + serviceInstance.setHost(uri.getHost()); + serviceInstance.setPort(uri.getPort()); + } + enhancedPluginContext.setServiceInstance(serviceInstance); + }) + .doOnSuccess(v -> { + enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime); + EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder() + .httpStatus(exchange.getResponse().getRawStatusCode()) + .httpHeaders(exchange.getResponse().getHeaders()) + .build(); + enhancedPluginContext.setResponse(enhancedResponseContext); + + // Run post enhanced plugins. + pluginRunner.run(POST, enhancedPluginContext); + }) + .doOnError(t -> { + enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime); + enhancedPluginContext.setThrowable(t); + + // Run exception enhanced plugins. + pluginRunner.run(EXCEPTION, enhancedPluginContext); + }) + .doFinally(v -> { + // Run finally enhanced plugins. + pluginRunner.run(FINALLY, enhancedPluginContext); + }); + } + + /** + * {@link ReactiveLoadBalancerClientFilter}.LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150. + */ + @Override + public int getOrder() { + return 10150 + 1; + } +} 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..2b62fc5d0 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporter.java @@ -0,0 +1,97 @@ +/* + * 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.util.Map; + +import com.tencent.cloud.common.constant.HeaderConstant; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.client.DefaultServiceInstance; +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.rpc.enhancement.plugin.EnhancedPluginType.EXCEPTION; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.FINALLY; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.POST; + +/** + * EnhancedWebClientReporter. + * + * @author sean yu + */ +public class EnhancedWebClientReporter implements ExchangeFilterFunction { + private final EnhancedPluginRunner pluginRunner; + + public EnhancedWebClientReporter(EnhancedPluginRunner pluginRunner) { + this.pluginRunner = pluginRunner; + } + + @Override + public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) { + EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext(); + + EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder() + .httpHeaders(request.headers()) + .httpMethod(request.method()) + .url(request.url()) + .build(); + enhancedPluginContext.setRequest(enhancedRequestContext); + + long startTime = System.currentTimeMillis(); + return next.exchange(request) + .doOnSubscribe(subscription -> { + Map<String, String> loadBalancerContext = MetadataContextHolder.get().getLoadbalancerMetadata(); + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); + serviceInstance.setServiceId(loadBalancerContext.get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID)); + serviceInstance.setHost(request.url().getHost()); + serviceInstance.setPort(request.url().getPort()); + enhancedPluginContext.setServiceInstance(serviceInstance); + }) + .doOnSuccess(response -> { + enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime); + + EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder() + .httpStatus(response.rawStatusCode()) + .httpHeaders(response.headers().asHttpHeaders()) + .build(); + enhancedPluginContext.setResponse(enhancedResponseContext); + + // Run post enhanced plugins. + pluginRunner.run(POST, enhancedPluginContext); + }) + .doOnError(t -> { + enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime); + enhancedPluginContext.setThrowable(t); + + // Run exception enhanced plugins. + pluginRunner.run(EXCEPTION, enhancedPluginContext); + }) + .doFinally(v -> { + // Run finally enhanced plugins. + pluginRunner.run(FINALLY, enhancedPluginContext); + }); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/PolarisLoadBalancerClientRequestTransformer.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/PolarisLoadBalancerClientRequestTransformer.java new file mode 100644 index 000000000..4e6913164 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/PolarisLoadBalancerClientRequestTransformer.java @@ -0,0 +1,42 @@ +/* + * 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 com.tencent.cloud.common.constant.HeaderConstant; +import com.tencent.cloud.common.metadata.MetadataContextHolder; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerClientRequestTransformer; +import org.springframework.web.reactive.function.client.ClientRequest; + +/** + * PolarisLoadBalancerClientRequestTransformer. + * + * @author sean yu + */ +public class PolarisLoadBalancerClientRequestTransformer implements LoadBalancerClientRequestTransformer { + + @Override + public ClientRequest transformRequest(ClientRequest request, ServiceInstance instance) { + if (instance != null) { + MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, instance.getServiceId()); + } + return request; + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedErrorZuulFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedErrorZuulFilter.java new file mode 100644 index 000000000..362e9be0b --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedErrorZuulFilter.java @@ -0,0 +1,127 @@ +/* + * 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.zuul; + +import java.util.ArrayList; +import java.util.Collection; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.exception.ZuulException; +import com.tencent.cloud.common.constant.ContextConstant; +import com.tencent.cloud.common.util.ZuulFilterUtils; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.netflix.ribbon.apache.RibbonApacheHttpResponse; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpHeaders; +import org.springframework.util.StringUtils; + +import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_PRE_ROUTE_TIME; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.EXCEPTION; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.FINALLY; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ERROR_TYPE; + +/** + * Polaris circuit breaker error-processing. Including reporting and fallback. + * This will remove throwable from Zuul context. + * + * @author Haotian Zhang + */ +public class EnhancedErrorZuulFilter extends ZuulFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedErrorZuulFilter.class); + + private final EnhancedPluginRunner pluginRunner; + + private final Environment environment; + + public EnhancedErrorZuulFilter(EnhancedPluginRunner pluginRunner, Environment environment) { + this.pluginRunner = pluginRunner; + this.environment = environment; + } + + @Override + public String filterType() { + return ERROR_TYPE; + } + + @Override + public int filterOrder() { + return Integer.MIN_VALUE; + } + + @Override + public boolean shouldFilter() { + RequestContext ctx = RequestContext.getCurrentContext(); + String enabled = environment.getProperty("spring.cloud.tencent.rpc-enhancement.reporter"); + return ctx.getThrowable() != null && (StringUtils.isEmpty(enabled) || enabled.equals("true")); + } + + @Override + public Object run() throws ZuulException { + RequestContext context = RequestContext.getCurrentContext(); + Object enhancedPluginContextObj = context.get(ContextConstant.Zuul.ENHANCED_PLUGIN_CONTEXT); + EnhancedPluginContext enhancedPluginContext; + if (enhancedPluginContextObj == null || !(enhancedPluginContextObj instanceof EnhancedPluginContext)) { + enhancedPluginContext = new EnhancedPluginContext(); + } + else { + enhancedPluginContext = (EnhancedPluginContext) enhancedPluginContextObj; + } + + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); + Object ribbonResponseObj = context.get("ribbonResponse"); + Object startTimeMilliObject = context.get(POLARIS_PRE_ROUTE_TIME); + Throwable throwable = context.getThrowable(); + RibbonApacheHttpResponse ribbonResponse; + if (throwable != null && ribbonResponseObj != null && ribbonResponseObj instanceof RibbonApacheHttpResponse + && startTimeMilliObject != null && startTimeMilliObject instanceof Long) { + HttpHeaders responseHeaders = new HttpHeaders(); + Collection<String> names = context.getResponse().getHeaderNames(); + for (String name : names) { + responseHeaders.put(name, new ArrayList<>(context.getResponse().getHeaders(name))); + } + EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder() + .httpStatus(context.getResponse().getStatus()) + .httpHeaders(responseHeaders) + .build(); + enhancedPluginContext.setResponse(enhancedResponseContext); + Long startTimeMilli = (Long) startTimeMilliObject; + enhancedPluginContext.setDelay(System.currentTimeMillis() - startTimeMilli); + enhancedPluginContext.setThrowable(throwable); + ribbonResponse = (RibbonApacheHttpResponse) ribbonResponseObj; + serviceInstance.setServiceId(ZuulFilterUtils.getServiceId(context)); + serviceInstance.setHost(ribbonResponse.getRequestedURI().getHost()); + serviceInstance.setPort(ribbonResponse.getRequestedURI().getPort()); + enhancedPluginContext.setServiceInstance(serviceInstance); + + // Run post enhanced plugins. + pluginRunner.run(EXCEPTION, enhancedPluginContext); + + // Run finally enhanced plugins. + pluginRunner.run(FINALLY, enhancedPluginContext); + } + return null; + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedPostZuulFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedPostZuulFilter.java new file mode 100644 index 000000000..ff1ba4095 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedPostZuulFilter.java @@ -0,0 +1,124 @@ +/* + * 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.zuul; + +import java.util.ArrayList; +import java.util.Collection; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.exception.ZuulException; +import com.tencent.cloud.common.constant.ContextConstant; +import com.tencent.cloud.common.util.ZuulFilterUtils; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.netflix.ribbon.apache.RibbonApacheHttpResponse; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpHeaders; +import org.springframework.util.StringUtils; + +import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_PRE_ROUTE_TIME; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.FINALLY; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.POST; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SEND_RESPONSE_FILTER_ORDER; + +/** + * Polaris circuit breaker implement in Zuul. + * + * @author Haotian Zhang + */ +public class EnhancedPostZuulFilter extends ZuulFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedPostZuulFilter.class); + + private final EnhancedPluginRunner pluginRunner; + + private final Environment environment; + + public EnhancedPostZuulFilter(EnhancedPluginRunner pluginRunner, Environment environment) { + this.pluginRunner = pluginRunner; + this.environment = environment; + } + + @Override + public String filterType() { + return POST_TYPE; + } + + @Override + public int filterOrder() { + return SEND_RESPONSE_FILTER_ORDER + 1; + } + + @Override + public boolean shouldFilter() { + String enabled = environment.getProperty("spring.cloud.tencent.rpc-enhancement.reporter"); + return StringUtils.isEmpty(enabled) || enabled.equals("true"); + } + + @Override + public Object run() throws ZuulException { + RequestContext context = RequestContext.getCurrentContext(); + Object enhancedPluginContextObj = context.get(ContextConstant.Zuul.ENHANCED_PLUGIN_CONTEXT); + EnhancedPluginContext enhancedPluginContext; + if (enhancedPluginContextObj == null || !(enhancedPluginContextObj instanceof EnhancedPluginContext)) { + enhancedPluginContext = new EnhancedPluginContext(); + } + else { + enhancedPluginContext = (EnhancedPluginContext) enhancedPluginContextObj; + } + + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); + Object ribbonResponseObj = context.get("ribbonResponse"); + Object startTimeMilliObject = context.get(POLARIS_PRE_ROUTE_TIME); + RibbonApacheHttpResponse ribbonResponse; + if (ribbonResponseObj != null && ribbonResponseObj instanceof RibbonApacheHttpResponse + && startTimeMilliObject != null && startTimeMilliObject instanceof Long) { + HttpHeaders responseHeaders = new HttpHeaders(); + Collection<String> names = context.getResponse().getHeaderNames(); + for (String name : names) { + responseHeaders.put(name, new ArrayList<>(context.getResponse().getHeaders(name))); + } + EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder() + .httpStatus(context.getResponse().getStatus()) + .httpHeaders(responseHeaders) + .build(); + enhancedPluginContext.setResponse(enhancedResponseContext); + Long startTimeMilli = (Long) startTimeMilliObject; + enhancedPluginContext.setDelay(System.currentTimeMillis() - startTimeMilli); + ribbonResponse = (RibbonApacheHttpResponse) ribbonResponseObj; + serviceInstance.setServiceId(ZuulFilterUtils.getServiceId(context)); + serviceInstance.setHost(ribbonResponse.getRequestedURI().getHost()); + serviceInstance.setPort(ribbonResponse.getRequestedURI().getPort()); + enhancedPluginContext.setServiceInstance(serviceInstance); + + // Run post enhanced plugins. + pluginRunner.run(POST, enhancedPluginContext); + + // Run finally enhanced plugins. + pluginRunner.run(FINALLY, enhancedPluginContext); + } + return null; + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedPreZuulFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedPreZuulFilter.java new file mode 100644 index 000000000..65484c131 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedPreZuulFilter.java @@ -0,0 +1,117 @@ +/* + * 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.zuul; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.Enumeration; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.exception.ZuulException; +import com.tencent.cloud.common.constant.ContextConstant; +import com.tencent.cloud.common.util.ZuulFilterUtils; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.core.env.Environment; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.util.StringUtils; + +import static com.tencent.cloud.common.constant.ContextConstant.Zuul.POLARIS_PRE_ROUTE_TIME; +import static com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType.PRE; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE; + +/** + * Polaris circuit breaker implement in Zuul. + * + * @author Haotian Zhang + */ +public class EnhancedPreZuulFilter extends ZuulFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedPreZuulFilter.class); + + private final EnhancedPluginRunner pluginRunner; + + private final Environment environment; + + public EnhancedPreZuulFilter(EnhancedPluginRunner pluginRunner, Environment environment) { + this.pluginRunner = pluginRunner; + this.environment = environment; + } + + @Override + public String filterType() { + return PRE_TYPE; + } + + @Override + public int filterOrder() { + return PRE_DECORATION_FILTER_ORDER + 1; + } + + @Override + public boolean shouldFilter() { + String enabled = environment.getProperty("spring.cloud.tencent.rpc-enhancement.reporter"); + return StringUtils.isEmpty(enabled) || enabled.equals("true"); + } + + @Override + public Object run() throws ZuulException { + EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext(); + RequestContext context = RequestContext.getCurrentContext(); + context.set(ContextConstant.Zuul.ENHANCED_PLUGIN_CONTEXT, enhancedPluginContext); + + try { + URI uri = new URI(context.getRequest() + .getScheme(), ZuulFilterUtils.getServiceId(context), ZuulFilterUtils.getPath(context), context.getRequest() + .getQueryString(), null); + HttpHeaders requestHeaders = new HttpHeaders(); + Enumeration<String> names = context.getRequest().getHeaderNames(); + while (names.hasMoreElements()) { + String name = names.nextElement(); + requestHeaders.put(name, Collections.list(context.getRequest().getHeaders(name))); + } + EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder() + .httpHeaders(requestHeaders) + .httpMethod(HttpMethod.resolve(context.getRequest().getMethod())) + .url(uri) + .build(); + + enhancedPluginContext.setRequest(enhancedRequestContext); + + // Run pre enhanced plugins. + pluginRunner.run(PRE, enhancedPluginContext); + + Object startTimeMilliObject = context.get(POLARIS_PRE_ROUTE_TIME); + if (startTimeMilliObject == null || !(startTimeMilliObject instanceof Long)) { + context.set(POLARIS_PRE_ROUTE_TIME, Long.valueOf(System.currentTimeMillis())); + } + } + catch (URISyntaxException e) { + LOGGER.error("Generate URI failed.", e); + } + return null; + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 9572b5ad7..a2a449b33 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -9,7 +9,7 @@ { "name": "spring.cloud.tencent.rpc-enhancement.reporter.enabled", "type": "java.lang.Boolean", - "defaultValue": false, + "defaultValue": true, "description": "Whether report call result to polaris." }, { 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..825da246f 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,29 +17,196 @@ package com.tencent.cloud.rpc.enhancement; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URISyntaxException; + +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.ApplicationContextAwareUtils; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; -import org.assertj.core.api.Assertions; +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.plugin.circuitbreaker.ResourceStat; +import com.tencent.polaris.api.pojo.RetStatus; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; +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.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +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.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + /** * Test For {@link AbstractPolarisReporterAdapter}. * * @author <a href="mailto:iskp.me@gmail.com">Elve.Xu</a> 2022/7/11 */ +@ExtendWith(MockitoExtension.class) public class AbstractPolarisReporterAdapterTest { + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + private final RpcEnhancementReporterProperties reporterProperties = new RpcEnhancementReporterProperties(); + @Mock + private SDKContext sdkContext; + + @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 testServiceCallResult() throws URISyntaxException { + APIConfig apiConfig = mock(APIConfig.class); + doReturn("0.0.0.0").when(apiConfig).getBindIP(); + + GlobalConfig globalConfig = mock(GlobalConfig.class); + doReturn(apiConfig).when(globalConfig).getAPI(); + + Configuration configuration = mock(Configuration.class); + doReturn(globalConfig).when(configuration).getGlobal(); + + doReturn(configuration).when(sdkContext).getConfig(); + + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(reporterProperties, sdkContext); + + ServiceCallResult serviceCallResult; + + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.add(RouterConstant.ROUTER_LABEL_HEADER, "{\"k1\":\"v1\"}"); + + serviceCallResult = adapter.createServiceCallResult( + "test", + null, + null, + new URI("http://0.0.0.0/"), + requestHeaders, + new HttpHeaders(), + 200, + 0, + null + ); + assertThat(serviceCallResult.getRetStatus()).isEqualTo(RetStatus.RetSuccess); + + serviceCallResult = adapter.createServiceCallResult( + "test", + null, + null, + new URI("http://0.0.0.0/"), + requestHeaders, + new HttpHeaders(), + 502, + 0, + null + ); + assertThat(serviceCallResult.getRetStatus()).isEqualTo(RetStatus.RetFail); + + serviceCallResult = adapter.createServiceCallResult( + "test", + null, + null, + new URI("http://0.0.0.0/"), + requestHeaders, + null, + null, + 0, + new SocketTimeoutException() + ); + assertThat(serviceCallResult.getRetStatus()).isEqualTo(RetStatus.RetTimeout); + + serviceCallResult = adapter.createServiceCallResult( + "test", + "0.0.0.0", + 8080, + new URI("/"), + requestHeaders, + new HttpHeaders(), + 200, + 0, + null + ); + assertThat(serviceCallResult.getRetStatus()).isEqualTo(RetStatus.RetSuccess); + assertThat(serviceCallResult.getHost()).isEqualTo("0.0.0.0"); + assertThat(serviceCallResult.getPort()).isEqualTo(8080); + } + + @Test + public void testResourceStat() throws URISyntaxException { + + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(reporterProperties, sdkContext); + + ResourceStat resourceStat; + + resourceStat = adapter.createInstanceResourceStat("test", + null, + null, + new URI("http://0.0.0.0/"), + 200, + 0, + null + ); + assertThat(resourceStat.getRetStatus()).isEqualTo(RetStatus.RetSuccess); + + resourceStat = adapter.createInstanceResourceStat("test", + null, + null, + new URI("http://0.0.0.0/"), + null, + 0, + new SocketTimeoutException() + ); + assertThat(resourceStat.getRetStatus()).isEqualTo(RetStatus.RetTimeout); + + resourceStat = adapter.createInstanceResourceStat("test", + null, + null, + new URI("http://0.0.0.0/"), + 200, + 0, + null + ); + assertThat(resourceStat.getRetStatus()).isEqualTo(RetStatus.RetSuccess); + } + @Test public void testApplyWithDefaultConfig() { RpcEnhancementReporterProperties properties = new RpcEnhancementReporterProperties(); // Mock Condition - SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties, sdkContext); // Assert - Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); - Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); - Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); + assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); + assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); } @Test @@ -49,12 +216,12 @@ public class AbstractPolarisReporterAdapterTest { properties.getStatuses().clear(); properties.setIgnoreInternalServerError(false); - SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties, sdkContext); // Assert - Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); - Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(true); - Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); + assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(true); + assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); } @Test @@ -64,12 +231,12 @@ public class AbstractPolarisReporterAdapterTest { properties.getStatuses().clear(); properties.setIgnoreInternalServerError(true); - SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties, sdkContext); // Assert - Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); - Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); - Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); + assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); + assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); } @Test @@ -79,12 +246,12 @@ public class AbstractPolarisReporterAdapterTest { properties.getStatuses().clear(); properties.getSeries().clear(); - SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties, sdkContext); // Assert - Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); - Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); - Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); + assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); + assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); } @Test @@ -95,13 +262,56 @@ public class AbstractPolarisReporterAdapterTest { properties.getSeries().clear(); properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR); - SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties, sdkContext); // Assert - Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); - Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); - Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(false); - Assertions.assertThat(adapter.apply(HttpStatus.FORBIDDEN)).isEqualTo(true); + assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); + assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(false); + 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, sdkContext); + + HttpHeaders headers = new HttpHeaders(); + RetStatus ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail); + assertThat(ret).isEqualTo(RetStatus.RetFail); + + headers.set(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetFlowControl.getDesc()); + ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail); + assertThat(ret).isEqualTo(RetStatus.RetFlowControl); + + headers.set(HeaderConstant.INTERNAL_CALLEE_RET_STATUS, RetStatus.RetReject.getDesc()); + ret = adapter.getRetStatusFromRequest(headers, RetStatus.RetFail); + 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, sdkContext); + + HttpHeaders headers = new HttpHeaders(); + String ruleName = adapter.getActiveRuleNameFromRequest(headers); + assertThat(ruleName).isEqualTo(""); + + headers.set(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME, "mock_rule"); + ruleName = adapter.getActiveRuleNameFromRequest(headers); + assertThat(ruleName).isEqualTo("mock_rule"); } /** @@ -109,8 +319,8 @@ public class AbstractPolarisReporterAdapterTest { */ public static class SimplePolarisReporterAdapter extends AbstractPolarisReporterAdapter { - public SimplePolarisReporterAdapter(RpcEnhancementReporterProperties properties) { - super(properties); + protected SimplePolarisReporterAdapter(RpcEnhancementReporterProperties reportProperties, SDKContext context) { + super(reportProperties, context); } } } 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 0b883096b..fe02ab397 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 @@ -19,10 +19,10 @@ package com.tencent.cloud.rpc.enhancement.config; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; import com.tencent.cloud.rpc.enhancement.feign.EnhancedFeignBeanPostProcessor; -import com.tencent.cloud.rpc.enhancement.feign.EnhancedFeignPluginRunner; -import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.ExceptionPolarisReporter; -import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.SuccessPolarisReporter; -import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; +import com.tencent.cloud.rpc.enhancement.plugin.reporter.ExceptionPolarisReporter; +import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter; +import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateInterceptor; import com.tencent.polaris.api.core.ConsumerAPI; import org.junit.jupiter.api.Test; @@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @@ -47,21 +48,20 @@ public class RpcEnhancementAutoConfigurationTest { .withConfiguration(AutoConfigurations.of( PolarisContextAutoConfiguration.class, RpcEnhancementAutoConfiguration.class, - PolarisRestTemplateAutoConfigurationTester.class)) - .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true"); + PolarisRestTemplateAutoConfigurationTester.class, + FeignLoadBalancerAutoConfiguration.class)) + .withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true", "spring.application.name=test", "spring.cloud.gateway.enabled=false"); @Test public void testDefaultInitialization() { this.contextRunner.run(context -> { assertThat(context).hasSingleBean(ConsumerAPI.class); - assertThat(context).hasSingleBean(EnhancedFeignPluginRunner.class); + assertThat(context).hasSingleBean(EnhancedPluginRunner.class); assertThat(context).hasSingleBean(EnhancedFeignBeanPostProcessor.class); assertThat(context).hasSingleBean(SuccessPolarisReporter.class); assertThat(context).hasSingleBean(ExceptionPolarisReporter.class); - assertThat(context).hasSingleBean(EnhancedRestTemplateReporter.class); + assertThat(context).hasSingleBean(EnhancedRestTemplateInterceptor.class); assertThat(context).hasSingleBean(RestTemplate.class); - RestTemplate restTemplate = context.getBean(RestTemplate.class); - assertThat(restTemplate.getErrorHandler() instanceof EnhancedRestTemplateReporter).isTrue(); }); } 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 48db48072..6ed9d8857 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,11 @@ import static org.springframework.http.HttpStatus.Series.SERVER_ERROR; * @author Haotian Zhang */ @ExtendWith(SpringExtension.class) -@SpringBootTest(classes = RpcEnhancementReporterPropertiesTest.TestApplication.class) +@SpringBootTest(classes = RpcEnhancementReporterPropertiesTest.TestApplication.class, properties = { + "spring.application.name=test", + "spring.cloud.gateway.enabled=false", + "spring.cloud.tencent.rpc-enhancement.reporter=true" +}) @ActiveProfiles("test") public class RpcEnhancementReporterPropertiesTest { @@ -55,6 +59,7 @@ public class RpcEnhancementReporterPropertiesTest { assertThat(rpcEnhancementReporterProperties.getStatuses()).isNotEmpty(); assertThat(rpcEnhancementReporterProperties.getStatuses().get(0)).isEqualTo(MULTIPLE_CHOICES); assertThat(rpcEnhancementReporterProperties.getStatuses().get(1)).isEqualTo(MOVED_PERMANENTLY); + assertThat(rpcEnhancementReporterProperties.isEnabled()).isEqualTo(true); } @SpringBootApplication 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..2ff9ff909 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 @@ -22,9 +22,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; -import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin; -import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; +import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; +import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType; import feign.Client; import feign.Request; import feign.RequestTemplate; @@ -51,7 +52,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 @@ -72,9 +73,9 @@ public class EnhancedFeignClientTest { fail("Exception encountered.", e); } - List<EnhancedFeignPlugin> enhancedFeignPlugins = getMockEnhancedFeignPlugins(); + List<EnhancedPlugin> enhancedPlugins = getMockEnhancedFeignPlugins(); try { - new EnhancedFeignClient(mock(Client.class), new DefaultEnhancedFeignPluginRunner(enhancedFeignPlugins)); + new EnhancedFeignClient(mock(Client.class), new DefaultEnhancedPluginRunner(enhancedPlugins)); } catch (Throwable e) { fail("Exception encountered.", e); @@ -103,7 +104,7 @@ public class EnhancedFeignClientTest { RequestTemplate requestTemplate = new RequestTemplate(); requestTemplate.feignTarget(target); - EnhancedFeignClient polarisFeignClient = new EnhancedFeignClient(delegate, new DefaultEnhancedFeignPluginRunner(getMockEnhancedFeignPlugins())); + EnhancedFeignClient polarisFeignClient = new EnhancedFeignClient(delegate, new DefaultEnhancedPluginRunner(getMockEnhancedFeignPlugins())); // 200 Response response = polarisFeignClient.execute(Request.create(Request.HttpMethod.GET, "http://localhost:8080/test", @@ -127,22 +128,22 @@ public class EnhancedFeignClientTest { } } - private List<EnhancedFeignPlugin> getMockEnhancedFeignPlugins() { - List<EnhancedFeignPlugin> enhancedFeignPlugins = new ArrayList<>(); + private List<EnhancedPlugin> getMockEnhancedFeignPlugins() { + List<EnhancedPlugin> enhancedPlugins = new ArrayList<>(); - enhancedFeignPlugins.add(new EnhancedFeignPlugin() { + enhancedPlugins.add(new EnhancedPlugin() { @Override - public EnhancedFeignPluginType getType() { - return EnhancedFeignPluginType.PRE; + public EnhancedPluginType getType() { + return EnhancedPluginType.PRE; } @Override - public void run(EnhancedFeignContext context) { + public void run(EnhancedPluginContext context) { } @Override - public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) { + public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) { } @@ -152,19 +153,19 @@ public class EnhancedFeignClientTest { } }); - enhancedFeignPlugins.add(new EnhancedFeignPlugin() { + enhancedPlugins.add(new EnhancedPlugin() { @Override - public EnhancedFeignPluginType getType() { - return EnhancedFeignPluginType.POST; + public EnhancedPluginType getType() { + return EnhancedPluginType.POST; } @Override - public void run(EnhancedFeignContext context) { + public void run(EnhancedPluginContext context) { } @Override - public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) { + public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) { } @@ -174,19 +175,19 @@ public class EnhancedFeignClientTest { } }); - enhancedFeignPlugins.add(new EnhancedFeignPlugin() { + enhancedPlugins.add(new EnhancedPlugin() { @Override - public EnhancedFeignPluginType getType() { - return EnhancedFeignPluginType.EXCEPTION; + public EnhancedPluginType getType() { + return EnhancedPluginType.EXCEPTION; } @Override - public void run(EnhancedFeignContext context) { + public void run(EnhancedPluginContext context) { } @Override - public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) { + public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) { } @@ -196,19 +197,19 @@ public class EnhancedFeignClientTest { } }); - enhancedFeignPlugins.add(new EnhancedFeignPlugin() { + enhancedPlugins.add(new EnhancedPlugin() { @Override - public EnhancedFeignPluginType getType() { - return EnhancedFeignPluginType.FINALLY; + public EnhancedPluginType getType() { + return EnhancedPluginType.FINALLY; } @Override - public void run(EnhancedFeignContext context) { + public void run(EnhancedPluginContext context) { } @Override - public void handlerThrowable(EnhancedFeignContext context, Throwable throwable) { + public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) { } @@ -218,7 +219,7 @@ public class EnhancedFeignClientTest { } }); - return enhancedFeignPlugins; + return enhancedPlugins; } diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/EnhancedFeignContextTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/EnhancedFeignContextTest.java deleted file mode 100644 index 0143fc844..000000000 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/EnhancedFeignContextTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.feign.plugin; - -import feign.Request; -import feign.Response; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * Test for {@link EnhancedFeignContext}. - * - * @author Haotian Zhang - */ -public class EnhancedFeignContextTest { - - @Test - public void testGetAndSet() { - EnhancedFeignContext enhancedFeignContext = new EnhancedFeignContext(); - enhancedFeignContext.setRequest(mock(Request.class)); - enhancedFeignContext.setOptions(mock(Request.Options.class)); - enhancedFeignContext.setResponse(mock(Response.class)); - enhancedFeignContext.setException(mock(Exception.class)); - assertThat(enhancedFeignContext.getRequest()).isNotNull(); - assertThat(enhancedFeignContext.getOptions()).isNotNull(); - assertThat(enhancedFeignContext.getResponse()).isNotNull(); - assertThat(enhancedFeignContext.getException()).isNotNull(); - } -} 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 deleted file mode 100644 index a123e8490..000000000 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ExceptionPolarisReporterTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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.feign.plugin.reporter; - -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 feign.Request; -import feign.Response; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * Test for {@link ExceptionPolarisReporter}. - * - * @author Haotian Zhang - */ -@ExtendWith(MockitoExtension.class) -public class ExceptionPolarisReporterTest { - - private static MockedStatic<ReporterUtils> mockedReporterUtils; - @Mock - private ConsumerAPI consumerAPI; - @Mock - private RpcEnhancementReporterProperties reporterProperties; - @InjectMocks - private ExceptionPolarisReporter exceptionPolarisReporter; - - @BeforeAll - static void beforeAll() { - mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class); - mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(Request.class), any(RetStatus.class))) - .thenReturn(mock(ServiceCallResult.class)); - } - - @AfterAll - static void afterAll() { - mockedReporterUtils.close(); - } - - @Test - public void testGetName() { - assertThat(exceptionPolarisReporter.getName()).isEqualTo(ExceptionPolarisReporter.class.getName()); - } - - @Test - public void testType() { - assertThat(exceptionPolarisReporter.getType()).isEqualTo(EnhancedFeignPluginType.EXCEPTION); - } - - @Test - public void testRun() { - // mock request - Request request = mock(Request.class); - // mock response - Response response = mock(Response.class); - - EnhancedFeignContext context = mock(EnhancedFeignContext.class); - doReturn(request).when(context).getRequest(); - doReturn(response).when(context).getResponse(); - // test not report - exceptionPolarisReporter.run(context); - verify(context, times(0)).getRequest(); - // test do report - doReturn(true).when(reporterProperties).isEnabled(); - exceptionPolarisReporter.run(context); - verify(context, times(1)).getRequest(); - } - - @Test - public void testHandlerThrowable() { - // mock request - Request request = mock(Request.class); - // mock response - Response response = mock(Response.class); - - EnhancedFeignContext context = new EnhancedFeignContext(); - context.setRequest(request); - context.setResponse(response); - exceptionPolarisReporter.handlerThrowable(context, new RuntimeException("Mock exception.")); - } -} 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 deleted file mode 100644 index 841592986..000000000 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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.feign.plugin.reporter; - -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 feign.Request; -import feign.Response; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * Test for {@link SuccessPolarisReporter}. - * - * @author Haotian Zhang - */ -@ExtendWith(MockitoExtension.class) -public class SuccessPolarisReporterTest { - - private static MockedStatic<ReporterUtils> mockedReporterUtils; - @Mock - private ConsumerAPI consumerAPI; - @Mock - private RpcEnhancementReporterProperties reporterProperties; - @InjectMocks - private SuccessPolarisReporter successPolarisReporter; - - @BeforeAll - static void beforeAll() { - mockedReporterUtils = Mockito.mockStatic(ReporterUtils.class); - mockedReporterUtils.when(() -> ReporterUtils.createServiceCallResult(any(Request.class), any(RetStatus.class))) - .thenReturn(mock(ServiceCallResult.class)); - } - - @AfterAll - static void afterAll() { - mockedReporterUtils.close(); - } - - @Test - public void testGetName() { - assertThat(successPolarisReporter.getName()).isEqualTo(SuccessPolarisReporter.class.getName()); - } - - @Test - public void testType() { - assertThat(successPolarisReporter.getType()).isEqualTo(EnhancedFeignPluginType.POST); - } - - @Test - public void testRun() { - // mock request - Request request = mock(Request.class); - // mock response - Response response = mock(Response.class); - doReturn(502).when(response).status(); - - EnhancedFeignContext context = mock(EnhancedFeignContext.class); - doReturn(request).when(context).getRequest(); - doReturn(response).when(context).getResponse(); - // test not report - successPolarisReporter.run(context); - verify(context, times(0)).getRequest(); - // test do report - doReturn(true).when(reporterProperties).isEnabled(); - successPolarisReporter.run(context); - verify(context, times(1)).getRequest(); - } - - @Test - public void testHandlerThrowable() { - // mock request - Request request = mock(Request.class); - // mock response - Response response = mock(Response.class); - - EnhancedFeignContext context = new EnhancedFeignContext(); - context.setRequest(request); - context.setResponse(response); - successPolarisReporter.handlerThrowable(context, new RuntimeException("Mock exception.")); - } -} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContextTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContextTest.java new file mode 100644 index 000000000..a516483f1 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContextTest.java @@ -0,0 +1,134 @@ +/* + * 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.plugin; + +import java.net.URI; +import java.util.Arrays; + +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.plugin.reporter.ExceptionPolarisReporter; +import com.tencent.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; +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.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; + +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.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; + +/** + * Test for {@link EnhancedPluginContext}. + * + * @author Haotian Zhang + */ +@ExtendWith(MockitoExtension.class) +public class EnhancedPluginContextTest { + + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + @Mock + private RpcEnhancementReporterProperties reporterProperties; + @Mock + private SDKContext sdkContext; + @Mock + private ConsumerAPI consumerAPI; + + @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 testGetAndSet() throws Throwable { + EnhancedRequestContext requestContext = new EnhancedRequestContext(); + requestContext.setHttpHeaders(new HttpHeaders()); + requestContext.setUrl(new URI("/")); + requestContext.setHttpMethod(HttpMethod.GET); + + EnhancedRequestContext requestContext1 = EnhancedRequestContext.builder() + .httpHeaders(requestContext.getHttpHeaders()) + .url(requestContext.getUrl()) + .httpMethod(requestContext.getHttpMethod()) + .build(); + assertThat(requestContext1.getUrl()).isEqualTo(requestContext.getUrl()); + + EnhancedResponseContext responseContext = new EnhancedResponseContext(); + responseContext.setHttpStatus(200); + responseContext.setHttpHeaders(new HttpHeaders()); + + EnhancedResponseContext responseContext1 = EnhancedResponseContext.builder() + .httpStatus(responseContext.getHttpStatus()) + .httpHeaders(responseContext.getHttpHeaders()) + .build(); + assertThat(responseContext1.getHttpStatus()).isEqualTo(responseContext.getHttpStatus()); + + EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext(); + enhancedPluginContext.setRequest(requestContext); + enhancedPluginContext.setResponse(responseContext); + enhancedPluginContext.setServiceInstance(new DefaultServiceInstance()); + enhancedPluginContext.setThrowable(mock(Exception.class)); + enhancedPluginContext.setDelay(0); + assertThat(enhancedPluginContext.getRequest()).isNotNull(); + assertThat(enhancedPluginContext.getResponse()).isNotNull(); + assertThat(enhancedPluginContext.getServiceInstance()).isNotNull(); + assertThat(enhancedPluginContext.getThrowable()).isNotNull(); + assertThat(enhancedPluginContext.getDelay()).isNotNull(); + + EnhancedPlugin enhancedPlugin = new SuccessPolarisReporter(reporterProperties, sdkContext, consumerAPI); + EnhancedPlugin enhancedPlugin1 = new ExceptionPolarisReporter(reporterProperties, sdkContext, consumerAPI); + EnhancedPluginRunner enhancedPluginRunner = new DefaultEnhancedPluginRunner(Arrays.asList(enhancedPlugin, enhancedPlugin1)); + enhancedPluginRunner.run(EnhancedPluginType.POST, enhancedPluginContext); + + EnhancedPlugin enhancedPlugin2 = mock(EnhancedPlugin.class); + doThrow(new RuntimeException()).when(enhancedPlugin2).run(any()); + doReturn(EnhancedPluginType.POST).when(enhancedPlugin2).getType(); + enhancedPluginRunner = new DefaultEnhancedPluginRunner(Arrays.asList(enhancedPlugin2)); + enhancedPluginRunner.run(EnhancedPluginType.POST, enhancedPluginContext); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/ExceptionPolarisReporterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/ExceptionPolarisReporterTest.java new file mode 100644 index 000000000..21c61ef92 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/ExceptionPolarisReporterTest.java @@ -0,0 +1,156 @@ +/* + * 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.plugin; + +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.cloud.rpc.enhancement.plugin.reporter.ExceptionPolarisReporter; +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.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; +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; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; + +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.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link ExceptionPolarisReporter}. + * + * @author Haotian Zhang + */ +@ExtendWith(MockitoExtension.class) +public class ExceptionPolarisReporterTest { + + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + @Mock + private RpcEnhancementReporterProperties reporterProperties; + @Mock + private SDKContext sdkContext; + @InjectMocks + private ExceptionPolarisReporter exceptionPolarisReporter; + @Mock + private ConsumerAPI consumerAPI; + + @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 testGetName() { + assertThat(exceptionPolarisReporter.getName()).isEqualTo(ExceptionPolarisReporter.class.getName()); + } + + @Test + public void testType() { + assertThat(exceptionPolarisReporter.getType()).isEqualTo(EnhancedPluginType.EXCEPTION); + } + + @Test + public void testRun() { + EnhancedPluginContext context = mock(EnhancedPluginContext.class); + // test not report + exceptionPolarisReporter.run(context); + verify(context, times(0)).getRequest(); + + doReturn(true).when(reporterProperties).isEnabled(); + + APIConfig apiConfig = mock(APIConfig.class); + doReturn("0.0.0.0").when(apiConfig).getBindIP(); + + GlobalConfig globalConfig = mock(GlobalConfig.class); + doReturn(apiConfig).when(globalConfig).getAPI(); + + Configuration configuration = mock(Configuration.class); + doReturn(globalConfig).when(configuration).getGlobal(); + + doReturn(configuration).when(sdkContext).getConfig(); + + EnhancedPluginContext pluginContext = new EnhancedPluginContext(); + EnhancedRequestContext request = EnhancedRequestContext.builder() + .httpMethod(HttpMethod.GET) + .url(URI.create("http://0.0.0.0/")) + .httpHeaders(new HttpHeaders()) + .build(); + EnhancedResponseContext response = EnhancedResponseContext.builder() + .httpStatus(200) + .build(); + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); + serviceInstance.setServiceId(SERVICE_PROVIDER); + + pluginContext.setRequest(request); + pluginContext.setResponse(response); + pluginContext.setServiceInstance(serviceInstance); + pluginContext.setThrowable(new RuntimeException()); + + exceptionPolarisReporter.run(pluginContext); + exceptionPolarisReporter.getOrder(); + exceptionPolarisReporter.getName(); + exceptionPolarisReporter.getType(); + } + + @Test + public void testHandlerThrowable() { + // mock request + EnhancedRequestContext request = mock(EnhancedRequestContext.class); + // mock response + EnhancedResponseContext response = mock(EnhancedResponseContext.class); + + EnhancedPluginContext context = new EnhancedPluginContext(); + context.setRequest(request); + context.setResponse(response); + exceptionPolarisReporter.handlerThrowable(context, new RuntimeException("Mock exception.")); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/SuccessPolarisReporterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/SuccessPolarisReporterTest.java new file mode 100644 index 000000000..9af4ca019 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/SuccessPolarisReporterTest.java @@ -0,0 +1,152 @@ +/* + * 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.plugin; + +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.cloud.rpc.enhancement.plugin.reporter.SuccessPolarisReporter; +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.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; +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; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.http.HttpMethod; + +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.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link SuccessPolarisReporter}. + * + * @author Haotian Zhang + */ +@ExtendWith(MockitoExtension.class) +public class SuccessPolarisReporterTest { + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + @Mock + private SDKContext sdkContext; + @Mock + private RpcEnhancementReporterProperties reporterProperties; + @InjectMocks + private SuccessPolarisReporter successPolarisReporter; + @Mock + private ConsumerAPI consumerAPI; + + @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 testGetName() { + assertThat(successPolarisReporter.getName()).isEqualTo(SuccessPolarisReporter.class.getName()); + } + + @Test + public void testType() { + assertThat(successPolarisReporter.getType()).isEqualTo(EnhancedPluginType.POST); + } + + @Test + public void testRun() { + + EnhancedPluginContext context = mock(EnhancedPluginContext.class); + // test not report + successPolarisReporter.run(context); + verify(context, times(0)).getRequest(); + + doReturn(true).when(reporterProperties).isEnabled(); + APIConfig apiConfig = mock(APIConfig.class); + doReturn("0.0.0.0").when(apiConfig).getBindIP(); + + GlobalConfig globalConfig = mock(GlobalConfig.class); + doReturn(apiConfig).when(globalConfig).getAPI(); + + Configuration configuration = mock(Configuration.class); + doReturn(globalConfig).when(configuration).getGlobal(); + + doReturn(configuration).when(sdkContext).getConfig(); + + EnhancedPluginContext pluginContext = new EnhancedPluginContext(); + EnhancedRequestContext request = EnhancedRequestContext.builder() + .httpMethod(HttpMethod.GET) + .url(URI.create("http://0.0.0.0/")) + .build(); + EnhancedResponseContext response = EnhancedResponseContext.builder() + .httpStatus(200) + .build(); + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); + serviceInstance.setServiceId(SERVICE_PROVIDER); + + pluginContext.setRequest(request); + pluginContext.setResponse(response); + pluginContext.setServiceInstance(serviceInstance); + + successPolarisReporter.run(pluginContext); + successPolarisReporter.getOrder(); + successPolarisReporter.getName(); + successPolarisReporter.getType(); + } + + @Test + public void testHandlerThrowable() { + // mock request + EnhancedRequestContext request = mock(EnhancedRequestContext.class); + // mock response + EnhancedResponseContext response = mock(EnhancedResponseContext.class); + + EnhancedPluginContext context = new EnhancedPluginContext(); + context.setRequest(request); + context.setResponse(response); + successPolarisReporter.handlerThrowable(context, new RuntimeException("Mock exception.")); + } +} 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/resttemplate/BlockingLoadBalancerClientAspectTest.java similarity index 51% rename from spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtilsTest.java rename to spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/BlockingLoadBalancerClientAspectTest.java index c39dd1633..82bf173ba 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/resttemplate/BlockingLoadBalancerClientAspectTest.java @@ -15,29 +15,28 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter; +package com.tencent.cloud.rpc.enhancement.resttemplate; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; - -import com.tencent.cloud.common.constant.RouterConstant; +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.common.metadata.StaticMetadataManager; +import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; -import com.tencent.polaris.api.pojo.RetStatus; -import com.tencent.polaris.api.rpc.ServiceCallResult; -import feign.Request; -import feign.RequestTemplate; -import feign.Target; +import org.aspectj.lang.ProceedingJoinPoint; 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.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.context.ApplicationContext; + 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; @@ -45,21 +44,26 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -/** - * Test for {@link ReporterUtils}. - * - * @author Haotian Zhang - */ @ExtendWith(MockitoExtension.class) -public class ReporterUtilsTest { +public class BlockingLoadBalancerClientAspectTest { private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + @Mock + private ProceedingJoinPoint proceedingJoinPoint; + + private BlockingLoadBalancerClientAspect aspect = new BlockingLoadBalancerClientAspect(); @BeforeAll static void beforeAll() { mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) .thenReturn("unit-test"); + ApplicationContext applicationContext = mock(ApplicationContext.class); + MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class); + StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class); + doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class); + doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class); + mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext).thenReturn(applicationContext); } @AfterAll @@ -74,35 +78,15 @@ public class ReporterUtilsTest { } @Test - public void testCreateServiceCallResult() { - // mock target - Target<?> target = mock(Target.class); - doReturn(SERVICE_PROVIDER).when(target).name(); - - // mock RequestTemplate.class - RequestTemplate requestTemplate = new RequestTemplate(); - requestTemplate.feignTarget(target); - try { - requestTemplate.header(RouterConstant.ROUTER_LABEL_HEADER, URLEncoder.encode("{\"k1\":\"v1\",\"k2\":\"v2\"}", UTF_8)); - } - catch (UnsupportedEncodingException e) { - throw new RuntimeException("unsupported charset exception " + UTF_8); - } - - // mock request - Request request = mock(Request.class); - doReturn(requestTemplate).when(request).requestTemplate(); - doReturn("http://1.1.1.1:2345/path").when(request).url(); - - ServiceCallResult serviceCallResult = ReporterUtils.createServiceCallResult(request, RetStatus.RetSuccess); - assertThat(serviceCallResult.getNamespace()).isEqualTo(NAMESPACE_TEST); - assertThat(serviceCallResult.getService()).isEqualTo(SERVICE_PROVIDER); - assertThat(serviceCallResult.getHost()).isEqualTo("1.1.1.1"); - assertThat(serviceCallResult.getPort()).isEqualTo(2345); - assertThat(serviceCallResult.getRetStatus()).isEqualTo(RetStatus.RetSuccess); - assertThat(serviceCallResult.getMethod()).isEqualTo("/path"); - assertThat(serviceCallResult.getCallerService().getNamespace()).isEqualTo(NAMESPACE_TEST); - assertThat(serviceCallResult.getCallerService().getService()).isEqualTo(SERVICE_PROVIDER); - assertThat(serviceCallResult.getLabels()).isEqualTo("k1:v1|k2:v2"); + public void test() throws Throwable { + ServiceInstance serviceInstance = mock(ServiceInstance.class); + doReturn("0.0.0.0").when(serviceInstance).getHost(); + doReturn(80).when(serviceInstance).getPort(); + doReturn(new Object[]{ serviceInstance }).when(proceedingJoinPoint).getArgs(); + aspect.invoke(proceedingJoinPoint); + aspect.pointcut(); + assertThat(MetadataContextHolder.get().getLoadbalancerMetadata().get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST)).isEqualTo("0.0.0.0"); + assertThat(MetadataContextHolder.get().getLoadbalancerMetadata().get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT)).isEqualTo("80"); } + } diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateInterceptorTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateInterceptorTest.java new file mode 100644 index 000000000..6b256f014 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateInterceptorTest.java @@ -0,0 +1,123 @@ +/* + * 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.resttemplate; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; + +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.StaticMetadataManager; +import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner; +import com.tencent.polaris.client.api.SDKContext; +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.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpResponse; + +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.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; + +@ExtendWith(MockitoExtension.class) +public class EnhancedRestTemplateInterceptorTest { + + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + @Mock + private RpcEnhancementReporterProperties reporterProperties; + @Mock + private SDKContext sdkContext; + @Mock + private ClientHttpRequestExecution mockClientHttpRequestExecution; + @Mock + private ClientHttpResponse mockClientHttpResponse; + @Mock + private HttpRequest mockHttpRequest; + @Mock + private HttpHeaders mockHttpHeaders; + + @BeforeAll + static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); + ApplicationContext applicationContext = mock(ApplicationContext.class); + MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class); + StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class); + doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class); + doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class); + mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext).thenReturn(applicationContext); + } + + @AfterAll + static void afterAll() { + mockedApplicationContextAwareUtils.close(); + } + + @BeforeEach + void setUp() { + MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST; + MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER; + } + + @Test + public void testRun() throws IOException, URISyntaxException { + + ClientHttpResponse actualResult; + final byte[] inputBody = null; + + URI uri = new URI("http://0.0.0.0/"); + doReturn(uri).when(mockHttpRequest).getURI(); + doReturn(HttpMethod.GET).when(mockHttpRequest).getMethod(); + doReturn(mockHttpHeaders).when(mockHttpRequest).getHeaders(); + doReturn(mockClientHttpResponse).when(mockClientHttpRequestExecution).execute(mockHttpRequest, inputBody); + + EnhancedRestTemplateInterceptor reporter = new EnhancedRestTemplateInterceptor(new DefaultEnhancedPluginRunner(new ArrayList<>())); + actualResult = reporter.intercept(mockHttpRequest, inputBody, mockClientHttpRequestExecution); + assertThat(actualResult).isEqualTo(mockClientHttpResponse); + + actualResult = reporter.intercept(mockHttpRequest, inputBody, mockClientHttpRequestExecution); + assertThat(actualResult).isEqualTo(mockClientHttpResponse); + + doThrow(new SocketTimeoutException()).when(mockClientHttpRequestExecution).execute(mockHttpRequest, inputBody); + assertThatThrownBy(() -> reporter.intercept(mockHttpRequest, inputBody, mockClientHttpRequestExecution)).isInstanceOf(SocketTimeoutException.class); + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporterTest.java deleted file mode 100644 index 2b54c25f0..000000000 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporterTest.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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.resttemplate; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.util.HashMap; -import java.util.Map; - -import com.tencent.cloud.common.metadata.MetadataContext; -import com.tencent.cloud.common.metadata.MetadataContextHolder; -import com.tencent.cloud.common.util.ApplicationContextAwareUtils; -import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; -import com.tencent.polaris.api.core.ConsumerAPI; -import org.checkerframework.checker.nullness.qual.NonNull; -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; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.context.ApplicationContext; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.client.AbstractClientHttpResponse; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.DefaultResponseErrorHandler; -import org.springframework.web.client.ResponseErrorHandler; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * Test for {@link EnhancedRestTemplateReporter}. - * - * @author lepdou 2022-09-06 - */ -@ExtendWith(MockitoExtension.class) -public class EnhancedRestTemplateReporterTest { - - private static MockedStatic<MetadataContextHolder> mockedMetadataContextHolder; - private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; - @Mock - private ConsumerAPI consumerAPI; - @Mock - private RpcEnhancementReporterProperties reporterProperties; - @Mock - private ResponseErrorHandler delegate; - @InjectMocks - private EnhancedRestTemplateReporter enhancedRestTemplateReporter; - - @InjectMocks - private EnhancedRestTemplateReporter enhancedRestTemplateReporter2; - - @BeforeAll - static void beforeAll() { - mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); - mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) - .thenReturn("caller"); - MetadataContext metadataContext = Mockito.mock(MetadataContext.class); - - // mock transitive metadata - Map<String, String> loadBalancerContext = new HashMap<>(); - loadBalancerContext.put("host", "1.1.1.1"); - loadBalancerContext.put("port", "8080"); - when(metadataContext.getLoadbalancerMetadata()).thenReturn(loadBalancerContext); - - mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class); - mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext); - } - - @AfterAll - static void afterAll() { - mockedApplicationContextAwareUtils.close(); - mockedMetadataContextHolder.close(); - } - - @BeforeEach - void setUp() { - enhancedRestTemplateReporter.setDelegateHandler(delegate); - } - - @Test - public void testSetApplicationContext() { - ApplicationContext applicationContext = mock(ApplicationContext.class); - - // test no ResponseErrorHandler - when(applicationContext.getBeanNamesForType(any(Class.class))) - .thenReturn(new String[] {"enhancedRestTemplateReporter"}); - enhancedRestTemplateReporter2.setApplicationContext(applicationContext); - assertThat(enhancedRestTemplateReporter2.getDelegateHandler()).isInstanceOf(DefaultResponseErrorHandler.class); - - // test one other ResponseErrorHandler - when(applicationContext.getBeanNamesForType(any(Class.class))) - .thenReturn(new String[] {"enhancedRestTemplateReporter", "mockedResponseErrorHandler"}); - when(applicationContext.getBean(anyString())).thenReturn(mock(MockedResponseErrorHandler.class)); - enhancedRestTemplateReporter2.setApplicationContext(applicationContext); - assertThat(enhancedRestTemplateReporter2.getDelegateHandler()).isInstanceOf(MockedResponseErrorHandler.class); - } - - @Test - public void testHasError() throws IOException { - when(delegate.hasError(any())).thenReturn(true); - - MockedClientHttpResponse response = new MockedClientHttpResponse(); - assertThat(enhancedRestTemplateReporter.hasError(response)).isTrue(); - - String realHasError = response.getHeaders().getFirst(EnhancedRestTemplateReporter.HEADER_HAS_ERROR); - assertThat(realHasError).isEqualTo("true"); - } - - @Test - public void testHandleHasError() throws IOException { - when(reporterProperties.isEnabled()).thenReturn(true); - when(delegate.hasError(any())).thenReturn(true); - - MockedClientHttpResponse response = new MockedClientHttpResponse(); - enhancedRestTemplateReporter.hasError(response); - - URI uri = mock(URI.class); - enhancedRestTemplateReporter.handleError(uri, HttpMethod.GET, response); - - verify(consumerAPI, times(2)).updateServiceCallResult(any()); - verify(delegate).handleError(uri, HttpMethod.GET, response); - } - - @Test - public void testHandleHasNotError() throws IOException { - when(reporterProperties.isEnabled()).thenReturn(true); - when(delegate.hasError(any())).thenReturn(false); - - MockedClientHttpResponse response = new MockedClientHttpResponse(); - enhancedRestTemplateReporter.hasError(response); - - URI uri = mock(URI.class); - enhancedRestTemplateReporter.handleError(uri, HttpMethod.GET, response); - - verify(consumerAPI, times(2)).updateServiceCallResult(any()); - verify(delegate, times(0)).handleError(uri, HttpMethod.GET, response); - } - - @Test - public void testReportSwitchOff() throws IOException { - when(reporterProperties.isEnabled()).thenReturn(false); - when(delegate.hasError(any())).thenReturn(true); - - MockedClientHttpResponse response = new MockedClientHttpResponse(); - enhancedRestTemplateReporter.hasError(response); - - URI uri = mock(URI.class); - enhancedRestTemplateReporter.handleError(uri, HttpMethod.GET, response); - - verify(consumerAPI, times(0)).updateServiceCallResult(any()); - verify(delegate).handleError(uri, HttpMethod.GET, response); - } - - static class MockedClientHttpResponse extends AbstractClientHttpResponse { - - private final HttpHeaders headers; - - MockedClientHttpResponse() { - this.headers = new HttpHeaders(); - } - - @Override - public int getRawStatusCode() { - return 0; - } - - @Override - public String getStatusText() { - return null; - } - - @Override - public void close() { - - } - - @Override - public InputStream getBody() throws IOException { - return null; - } - - @Override - public HttpHeaders getHeaders() { - return headers; - } - - @Override - public HttpStatus getStatusCode() throws IOException { - return HttpStatus.OK; - } - } - - private static class MockedResponseErrorHandler extends DefaultResponseErrorHandler { - - @Override - public void handleError(@NonNull ClientHttpResponse response) { - } - - } -} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilterTest.java new file mode 100644 index 000000000..69867c6ab --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilterTest.java @@ -0,0 +1,128 @@ +/* + * 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.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; + +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.StaticMetadataManager; +import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner; +import com.tencent.polaris.client.api.SDKContext; +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.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.server.ServerWebExchange; + +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.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; + +@ExtendWith(MockitoExtension.class) +public class EnhancedGatewayGlobalFilterTest { + + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + @Mock + ServerWebExchange exchange; + @Mock + GatewayFilterChain chain; + @Mock + ServerHttpResponse response; + @Mock + ServerHttpRequest request; + @Mock + private RpcEnhancementReporterProperties reporterProperties; + @Mock + private SDKContext sdkContext; + + @BeforeAll + static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); + ApplicationContext applicationContext = mock(ApplicationContext.class); + MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class); + StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class); + doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class); + doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class); + mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext) + .thenReturn(applicationContext); + } + + @AfterAll + static void afterAll() { + mockedApplicationContextAwareUtils.close(); + } + + @BeforeEach + void setUp() { + MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST; + MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER; + } + + @Test + public void testRun() throws URISyntaxException { + + doReturn(new URI("http://0.0.0.0/")).when(request).getURI(); + doReturn(new HttpHeaders()).when(request).getHeaders(); + doReturn(HttpMethod.GET).when(request).getMethod(); + doReturn(new HttpHeaders()).when(response).getHeaders(); + doReturn(Mono.empty()).when(chain).filter(exchange); + Route route = mock(Route.class); + URI uri = new URI("http://TEST/"); + doReturn(uri).when(route).getUri(); + doReturn(route).when(exchange).getAttribute(GATEWAY_ROUTE_ATTR); + doReturn(new URI("http://0.0.0.0/")).when(exchange).getAttribute(GATEWAY_REQUEST_URL_ATTR); + doReturn(request).when(exchange).getRequest(); + doReturn(response).when(exchange).getResponse(); + + EnhancedGatewayGlobalFilter reporter = new EnhancedGatewayGlobalFilter(new DefaultEnhancedPluginRunner(new ArrayList<>())); + reporter.getOrder(); + + reporter.filter(exchange, chain).block(); + + doReturn(Mono.error(new RuntimeException())).when(chain).filter(exchange); + + assertThatThrownBy(() -> reporter.filter(exchange, chain).block()).isInstanceOf(RuntimeException.class); + + } +} 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 ba55b6f2e..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 @@ -41,7 +41,9 @@ public class StatConfigModifierTest { .withPropertyValues("spring.cloud.polaris.stat.enabled=true") .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.cloud.polaris.stat.path=/xxx") + .withPropertyValues("spring.application.name=test") + .withPropertyValues("spring.cloud.gateway.enabled=false"); private final ApplicationContextRunner pushContextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(TestApplication.class)) @@ -49,12 +51,16 @@ public class StatConfigModifierTest { .withPropertyValues("spring.cloud.polaris.stat.enabled=true") .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.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.cloud.polaris.stat.enabled=false") + .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..f8a4a673a --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientReporterTest.java @@ -0,0 +1,120 @@ +/* + * 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 java.net.URISyntaxException; +import java.util.ArrayList; + +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.StaticMetadataManager; +import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner; +import com.tencent.polaris.client.api.SDKContext; +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.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Mono; + +import org.springframework.context.ApplicationContext; +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 org.springframework.web.reactive.function.client.ExchangeFunction; + +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.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +@ExtendWith(MockitoExtension.class) +public class EnhancedWebClientReporterTest { + + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + @Mock + private RpcEnhancementReporterProperties reporterProperties; + @Mock + private SDKContext sdkContext; + @Mock + private ClientRequest clientRequest; + @Mock + private ExchangeFunction exchangeFunction; + @Mock + private ClientResponse clientResponse; + + @BeforeAll + static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); + ApplicationContext applicationContext = mock(ApplicationContext.class); + MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class); + StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class); + doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class); + doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class); + mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext).thenReturn(applicationContext); + } + + @AfterAll + static void afterAll() { + mockedApplicationContextAwareUtils.close(); + } + + @BeforeEach + void setUp() { + MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST; + MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER; + } + @Test + public void testRun() throws URISyntaxException { + + doReturn(new URI("http://0.0.0.0/")).when(clientRequest).url(); + doReturn(new HttpHeaders()).when(clientRequest).headers(); + doReturn(HttpMethod.GET).when(clientRequest).method(); + ClientResponse.Headers headers = mock(ClientResponse.Headers.class); + doReturn(headers).when(clientResponse).headers(); + doReturn(Mono.just(clientResponse)).when(exchangeFunction).exchange(any()); + + EnhancedWebClientReporter reporter = new EnhancedWebClientReporter(new DefaultEnhancedPluginRunner(new ArrayList<>())); + ClientResponse clientResponse1 = reporter.filter(clientRequest, exchangeFunction).block(); + assertThat(clientResponse1).isEqualTo(clientResponse); + + ClientResponse clientResponse2 = reporter.filter(clientRequest, exchangeFunction).block(); + assertThat(clientResponse2).isEqualTo(clientResponse); + + doReturn(Mono.error(new RuntimeException())).when(exchangeFunction).exchange(any()); + + assertThatThrownBy(() -> reporter.filter(clientRequest, exchangeFunction).block()).isInstanceOf(RuntimeException.class); + + + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/webclient/PolarisLoadBalancerClientRequestTransformerTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/webclient/PolarisLoadBalancerClientRequestTransformerTest.java new file mode 100644 index 000000000..811b7c5eb --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/webclient/PolarisLoadBalancerClientRequestTransformerTest.java @@ -0,0 +1,90 @@ +/* + * 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 com.tencent.cloud.common.constant.HeaderConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.metadata.StaticMetadataManager; +import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +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.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.context.ApplicationContext; +import org.springframework.web.reactive.function.client.ClientRequest; + +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.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +@ExtendWith(MockitoExtension.class) +public class PolarisLoadBalancerClientRequestTransformerTest { + + private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils; + + private PolarisLoadBalancerClientRequestTransformer transformer = new PolarisLoadBalancerClientRequestTransformer(); + + @Mock + private ClientRequest clientRequest; + + @Mock + private ServiceInstance serviceInstance; + + @BeforeAll + static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); + ApplicationContext applicationContext = mock(ApplicationContext.class); + MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class); + StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class); + doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class); + doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class); + mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext).thenReturn(applicationContext); + } + + @AfterAll + static void afterAll() { + mockedApplicationContextAwareUtils.close(); + } + + @BeforeEach + void setUp() { + MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST; + MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER; + } + + @Test + public void test() throws Throwable { + doReturn("test").when(serviceInstance).getServiceId(); + transformer.transformRequest(clientRequest, serviceInstance); + assertThat(MetadataContextHolder.get().getLoadbalancerMetadata().get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID)).isEqualTo("test"); + } +}