From bae5ead5fcd77faf3e7e24378f314e4104b74141 Mon Sep 17 00:00:00 2001 From: Haotian Zhang <928016560@qq.com> Date: Thu, 25 Aug 2022 17:15:13 +0800 Subject: [PATCH] add feature-env plugin & add spring cloud gateway staining plugin. (#532) --- CHANGELOG.md | 1 + .../CustomTransitiveMetadataResolver.java | 35 +- .../CustomTransitiveMetadataResolverTest.java | 23 +- .../ratelimit/RateLimitRuleLabelResolver.java | 2 +- .../filter/QuotaCheckReactiveFilter.java | 4 +- .../filter/QuotaCheckServletFilter.java | 4 +- .../filter/QuotaCheckReactiveFilterTest.java | 8 +- .../filter/QuotaCheckServletFilterTest.java | 16 +- .../polaris/router/PolarisRouterContext.java | 68 +++- ...arisRouterServiceInstanceListSupplier.java | 83 ++--- .../router/RouterRuleLabelResolver.java | 2 +- ...BalancerInterceptorBeanPostProcessor.java} | 12 +- ...alancerClientFilterBeanPostProcessor.java} | 15 +- .../router/config/FeignAutoConfiguration.java | 6 +- .../config/LoadBalancerConfiguration.java | 26 +- .../config/RouterAutoConfiguration.java | 39 ++- .../PolarisMetadataRouterProperties.java | 3 +- .../PolarisNearByRouterProperties.java | 3 +- .../PolarisRuleBasedRouterProperties.java | 3 +- .../feign/FeignExpressionLabelUtils.java | 2 +- .../feign/RouterLabelFeignInterceptor.java | 15 +- .../MetadataRouterRequestInterceptor.java | 57 ++++ .../NearbyRouterRequestInterceptor.java | 53 +++ .../RuleBasedRouterRequestInterceptor.java | 59 ++++ .../PolarisLoadBalancerInterceptor.java | 14 +- ...larisReactiveLoadBalancerClientFilter.java | 14 +- .../router/spi/RouterRequestInterceptor.java | 37 +++ .../router/spi/RouterResponseInterceptor.java | 38 +++ .../router/PolarisRouterContextTest.java | 67 +++- ...RouterServiceInstanceListSupplierTest.java | 59 ++-- ...ncerInterceptorBeanPostProcessorTest.java} | 18 +- ...cerClientFilterBeanPostProcessorTest.java} | 18 +- .../endpoint/PolarisRouterEndpointTest.java | 3 +- .../RouterLabelFeignInterceptorTest.java | 8 +- .../PolarisLoadBalancerInterceptorTest.java | 16 +- ...sReactiveLoadBalancerClientFilterTest.java | 8 +- .../common/constant/MetadataConstant.java | 19 +- .../config/MetadataAutoConfiguration.java | 15 - .../gateway/MetadataFirstScgFilter.java | 64 ---- .../tencent/cloud/common/rule/Condition.java | 65 ++++ .../cloud/common/rule/ConditionUtils.java | 48 +++ .../com/tencent/cloud/common/rule/KVPair.java | 54 +++ .../cloud/common/rule/KVPairUtils.java | 49 +++ .../tencent/cloud/common/rule/Operation.java | 133 ++++++++ .../common/util/ExpressionLabelUtils.java | 314 ------------------ .../cloud/common/util/JacksonUtils.java | 10 + .../expresstion/ExpressionLabelUtils.java | 135 ++++++++ .../ServletExpressionLabelUtils.java | 96 ++++++ .../SpringWebExpressionLabelUtils.java | 161 +++++++++ .../config/MetadataAutoConfigurationTest.java | 10 - .../cloud/common/rule/OperationTest.java | 97 ++++++ .../common/util/ExpressionLabelUtilsTest.java | 9 +- spring-cloud-tencent-coverage/pom.xml | 10 + spring-cloud-tencent-dependencies/pom.xml | 13 + .../gateway-callee-service/pom.xml | 7 +- .../gateway-callee-service2/pom.xml | 5 + .../gateway-scg-service/pom.xml | 10 + .../src/main/resources/bootstrap.yml | 12 +- .../README-zh.md | 174 ++++++++++ .../README.md | 179 ++++++++++ .../base/base-backend/pom.xml | 16 + .../basebackend/BackendController.java | 44 +++ .../basebackend/BaseBackendApplication.java | 34 ++ .../src/main/resources/bootstrap.yml | 15 + .../base/base-front/pom.xml | 16 + .../basefront/BaseFrontApplication.java | 34 ++ .../featureenv/basefront/FrontController.java | 51 +++ .../featureenv/basefront/MiddleService.java | 33 ++ .../src/main/resources/bootstrap.yml | 15 + .../base/base-middle/pom.xml | 18 + .../featureenv/basemiddle/BackendService.java | 33 ++ .../basemiddle/BaseMiddleApplication.java | 34 ++ .../basemiddle/MiddleController.java | 51 +++ .../src/main/resources/bootstrap.yml | 15 + .../base/pom.xml | 33 ++ .../feature1/feature1-backend/pom.xml | 16 + .../feature1backend/BackendController.java | 51 +++ .../Feature1BackendApplication.java | 34 ++ .../src/main/resources/bootstrap.yml | 19 ++ .../feature1/feature1-middle/pom.xml | 16 + .../feature1middle/BackendService.java | 33 ++ .../Feature1MiddleApplication.java | 34 ++ .../feature1middle/MiddleController.java | 58 ++++ .../src/main/resources/bootstrap.yml | 19 ++ .../feature1/pom.xml | 32 ++ .../feature2/feature2-backend/pom.xml | 16 + .../feature2backend/BackendController.java | 51 +++ .../Feature2BackendApplication.java | 34 ++ .../src/main/resources/bootstrap.yml | 19 ++ .../feature2/feature2-front/pom.xml | 17 + .../Feature2FrontApplication.java | 34 ++ .../feature2front/FrontController.java | 58 ++++ .../feature2front/MiddleService.java | 33 ++ .../src/main/resources/bootstrap.yml | 19 ++ .../feature2/pom.xml | 32 ++ .../featureenv-gateway/pom.xml | 30 ++ .../gateway/FeatureEnvScgApplication.java | 33 ++ .../src/main/resources/bootstrap.yml | 68 ++++ .../imgs/structs.png | Bin 0 -> 56389 bytes .../polaris-router-featureenv-example/pom.xml | 34 ++ spring-cloud-tencent-examples/pom.xml | 45 +-- spring-cloud-tencent-plugin-starters/pom.xml | 2 + .../pom.xml | 30 ++ .../FeatureEnvAutoConfiguration.java | 42 +++ .../featureenv/FeatureEnvProperties.java | 39 +++ .../FeatureEnvRouterRequestInterceptor.java | 65 ++++ ...itional-spring-configuration-metadata.json | 10 + .../main/resources/META-INF/spring.factories | 2 + ...eatureEnvRouterRequestInterceptorTest.java | 104 ++++++ .../pom.xml | 34 ++ .../gateway/SCGPluginsAutoConfiguration.java | 84 +++++ .../gateway/staining/StainingProperties.java | 39 +++ .../gateway/staining/TrafficStainer.java | 38 +++ .../TrafficStainingGatewayFilter.java | 119 +++++++ .../staining/rule/RuleStainingExecutor.java | 70 ++++ .../staining/rule/RuleStainingProperties.java | 73 ++++ .../staining/rule/RuleTrafficStainer.java | 57 ++++ .../gateway/staining/rule/StainingRule.java | 78 +++++ .../staining/rule/StainingRuleManager.java | 80 +++++ ...itional-spring-configuration-metadata.json | 40 +++ .../main/resources/META-INF/spring.factories | 2 + .../TrafficStainerGatewayFilterTest.java | 89 +++++ .../rule/RuleStainingExecutorTest.java | 181 ++++++++++ .../rule/StainingRuleManagerTest.java | 133 ++++++++ 124 files changed, 4566 insertions(+), 650 deletions(-) rename spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/{resttemplate/PolarisLoadBalancerBeanPostProcessor.java => beanprocessor/LoadBalancerInterceptorBeanPostProcessor.java} (83%) rename spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/{scg/PolarisLoadBalancerClientBeanPostProcessor.java => beanprocessor/ReactiveLoadBalancerClientFilterBeanPostProcessor.java} (81%) rename spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/{ => properties}/PolarisMetadataRouterProperties.java (95%) rename spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/{ => properties}/PolarisNearByRouterProperties.java (95%) rename spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/{ => properties}/PolarisRuleBasedRouterProperties.java (95%) create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/MetadataRouterRequestInterceptor.java create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/NearbyRouterRequestInterceptor.java create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/RuleBasedRouterRequestInterceptor.java create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterRequestInterceptor.java create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterResponseInterceptor.java rename spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/{resttemplate/PolarisLoadBalancerBeanPostProcessorTest.java => beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java} (81%) rename spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/{scg/PolarisLoadBalancerClientBeanPostProcessorTest.java => beanprocessor/ReactiveLoadBalancerClientFilterBeanPostProcessorTest.java} (82%) delete mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/filter/gateway/MetadataFirstScgFilter.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Condition.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/ConditionUtils.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPair.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPairUtils.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Operation.java delete mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ServletExpressionLabelUtils.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/SpringWebExpressionLabelUtils.java create mode 100644 spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/rule/OperationTest.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/README-zh.md create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/README.md create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/basebackend/BackendController.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/basebackend/BaseBackendApplication.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/basefront/BaseFrontApplication.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/basefront/FrontController.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/basefront/MiddleService.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/basemiddle/BackendService.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/basemiddle/BaseMiddleApplication.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/basemiddle/MiddleController.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/base/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1backend/BackendController.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1backend/Feature1BackendApplication.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1middle/BackendService.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1middle/Feature1MiddleApplication.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1middle/MiddleController.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2backend/BackendController.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2backend/Feature2BackendApplication.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2front/Feature2FrontApplication.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2front/FrontController.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2front/MiddleService.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/src/main/java/com/tencent/cloud/polaris/featureenv/gateway/FeatureEnvScgApplication.java create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/imgs/structs.png create mode 100644 spring-cloud-tencent-examples/polaris-router-featureenv-example/pom.xml create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/pom.xml create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvAutoConfiguration.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvProperties.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptor.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/test/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptorTest.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/pom.xml create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfiguration.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/StainingProperties.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilter.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutor.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingProperties.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainer.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRule.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManager.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainerGatewayFilterTest.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutorTest.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManagerTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 71f459460..aea8da09a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,3 +22,4 @@ - [Code optimization for rpc-enhancement module](https://github.com/Tencent/spring-cloud-tencent/pull/525) - [Feature: Optimized configuration update](https://github.com/Tencent/spring-cloud-tencent/pull/527) - [Feature:support pushGateway push metrics](https://github.com/Tencent/spring-cloud-tencent/pull/531) +- [add feature-env plugin & add spring cloud gateway staining plugin](https://github.com/Tencent/spring-cloud-tencent/pull/532) diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolver.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolver.java index f11c39182..f37ff1405 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolver.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolver.java @@ -25,6 +25,7 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; +import com.tencent.cloud.common.constant.MetadataConstant; import org.apache.commons.lang.StringUtils; import org.springframework.http.HttpHeaders; @@ -37,8 +38,7 @@ import org.springframework.web.server.ServerWebExchange; * @author lepdou 2022-05-20 */ public final class CustomTransitiveMetadataResolver { - private static final String TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-"; - private static final int TRANSITIVE_HEADER_PREFIX_LENGTH = TRANSITIVE_HEADER_PREFIX.length(); + private CustomTransitiveMetadataResolver() { } @@ -48,12 +48,20 @@ public final class CustomTransitiveMetadataResolver { HttpHeaders headers = exchange.getRequest().getHeaders(); for (Map.Entry> entry : headers.entrySet()) { String key = entry.getKey(); - - if (StringUtils.isNotBlank(key) && - StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX) + if (StringUtils.isBlank(key)) { + continue; + } + // resolve sct transitive header + if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX) && !CollectionUtils.isEmpty(entry.getValue())) { + String sourceKey = StringUtils.substring(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX_LENGTH); + result.put(sourceKey, entry.getValue().get(0)); + } - String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH); + //resolve polaris transitive header + if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX) + && !CollectionUtils.isEmpty(entry.getValue())) { + String sourceKey = StringUtils.substring(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH); result.put(sourceKey, entry.getValue().get(0)); } } @@ -67,12 +75,21 @@ public final class CustomTransitiveMetadataResolver { Enumeration headers = request.getHeaderNames(); while (headers.hasMoreElements()) { String key = headers.nextElement(); + if (StringUtils.isBlank(key)) { + continue; + } - if (StringUtils.isNotBlank(key) && - StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX) + // resolve sct transitive header + if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX) && StringUtils.isNotBlank(request.getHeader(key))) { + String sourceKey = StringUtils.substring(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX_LENGTH); + result.put(sourceKey, request.getHeader(key)); + } - String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH); + // resolve polaris transitive header + if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX) + && StringUtils.isNotBlank(request.getHeader(key))) { + String sourceKey = StringUtils.substring(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH); result.put(sourceKey, request.getHeader(key)); } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolverTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolverTest.java index 9dc83bd51..a1b286456 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolverTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolverTest.java @@ -34,7 +34,7 @@ import org.springframework.mock.web.server.MockServerWebExchange; public class CustomTransitiveMetadataResolverTest { @Test - public void test() { + public void testSCTTransitiveMetadata() { MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest.get(""); builder.header("X-SCT-Metadata-Transitive-a", "test"); MockServerWebExchange exchange = MockServerWebExchange.from(builder); @@ -44,11 +44,30 @@ public class CustomTransitiveMetadataResolverTest { } @Test - public void testServlet() { + public void testPolarisTransitiveMetadata() { + MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest.get(""); + builder.header("X-Polaris-Metadata-Transitive-a", "test"); + MockServerWebExchange exchange = MockServerWebExchange.from(builder); + Map resolve = CustomTransitiveMetadataResolver.resolve(exchange); + Assertions.assertThat(resolve.size()).isEqualTo(1); + Assertions.assertThat(resolve.get("a")).isEqualTo("test"); + } + + @Test + public void testSCTServletTransitiveMetadata() { MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader("X-SCT-Metadata-Transitive-a", "test"); Map resolve = CustomTransitiveMetadataResolver.resolve(request); Assertions.assertThat(resolve.size()).isEqualTo(1); Assertions.assertThat(resolve.get("a")).isEqualTo("test"); } + + @Test + public void testPolarisServletTransitiveMetadata() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("X-Polaris-Metadata-Transitive-a", "test"); + Map resolve = CustomTransitiveMetadataResolver.resolve(request); + Assertions.assertThat(resolve.size()).isEqualTo(1); + Assertions.assertThat(resolve.get("a")).isEqualTo("test"); + } } 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 4c29cb3ad..37e77fd12 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 @@ -24,7 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils; import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.polaris.client.pb.ModelProto; import com.tencent.polaris.client.pb.RateLimitProto; 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 cdca48224..cb95857ba 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 @@ -28,7 +28,7 @@ import javax.annotation.PostConstruct; import com.google.common.collect.Maps; import com.tencent.cloud.common.metadata.MetadataContext; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +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; @@ -161,6 +161,6 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { private Map getRuleExpressionLabels(ServerWebExchange exchange, String namespace, String service) { Set expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service); - return ExpressionLabelUtils.resolve(exchange, expressionLabels); + 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 8497bdf4e..82c3d7915 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 @@ -31,7 +31,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.tencent.cloud.common.metadata.MetadataContext; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +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; @@ -158,6 +158,6 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter { private Map getRuleExpressionLabels(HttpServletRequest request, String namespace, String service) { Set expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service); - return ExpressionLabelUtils.resolve(request, expressionLabels); + return ServletExpressionLabelUtils.resolve(request, expressionLabels); } } 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 b640ce689..4d1b8a29d 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 @@ -26,7 +26,7 @@ import java.util.concurrent.CountDownLatch; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +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; @@ -75,15 +75,15 @@ import static org.mockito.Mockito.when; public class QuotaCheckReactiveFilterTest { private static MockedStatic mockedApplicationContextAwareUtils; - private static MockedStatic expressionLabelUtilsMockedStatic; + private static MockedStatic expressionLabelUtilsMockedStatic; private final PolarisRateLimiterLabelReactiveResolver labelResolver = exchange -> Collections.singletonMap("ReactiveResolver", "ReactiveResolver"); private QuotaCheckReactiveFilter quotaCheckReactiveFilter; @BeforeClass public static void beforeClass() { - expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class); - when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())) + expressionLabelUtilsMockedStatic = mockStatic(SpringWebExpressionLabelUtils.class); + when(SpringWebExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())) .thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver")); mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); 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 b5a0a62fd..080ed2e6f 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 @@ -30,7 +30,7 @@ import javax.servlet.http.HttpServletRequest; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +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.spi.PolarisRateLimiterLabelServletResolver; @@ -74,7 +74,7 @@ import static org.mockito.Mockito.when; public class QuotaCheckServletFilterTest { private static MockedStatic mockedApplicationContextAwareUtils; - private static MockedStatic expressionLabelUtilsMockedStatic; + private static MockedStatic expressionLabelUtilsMockedStatic; private final PolarisRateLimiterLabelServletResolver labelResolver = exchange -> Collections.singletonMap("ServletResolver", "ServletResolver"); private QuotaCheckServletFilter quotaCheckServletFilter; @@ -82,8 +82,8 @@ public class QuotaCheckServletFilterTest { @BeforeClass public static void beforeClass() { - expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class); - when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())) + expressionLabelUtilsMockedStatic = mockStatic(SpringWebExpressionLabelUtils.class); + when(SpringWebExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())) .thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver")); mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); @@ -129,10 +129,9 @@ public class QuotaCheckServletFilterTest { RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class); when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.emptySet()); - this.quotaCheckServletFilter = - new QuotaCheckServletFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver); - this.quotaCheckWithHtmlRejectTipsServletFilter = - new QuotaCheckServletFilter(limitAPI, labelResolver, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleLabelResolver); + this.quotaCheckServletFilter = new QuotaCheckServletFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver); + this.quotaCheckWithHtmlRejectTipsServletFilter = new QuotaCheckServletFilter( + limitAPI, labelResolver, polarisRateLimitWithHtmlRejectTipsProperties, rateLimitRuleLabelResolver); } @Test @@ -224,6 +223,7 @@ public class QuotaCheckServletFilterTest { assertThat(response.getStatus()).isEqualTo(419); assertThat(response.getContentAsString()).isEqualTo("RejectRequestTips提示消息"); + // Exception MetadataContext.LOCAL_SERVICE = "TestApp4"; quotaCheckServletFilter.doFilterInternal(request, response, filterChain); diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterContext.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterContext.java index 1e05d2ec6..618b3c546 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterContext.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterContext.java @@ -18,11 +18,17 @@ package com.tencent.cloud.polaris.router; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedCaseInsensitiveMap; /** * the context for router. @@ -32,13 +38,13 @@ import org.springframework.util.CollectionUtils; public class PolarisRouterContext { /** - * the label for rule router. + * the labels for rule router, contain transitive metadata. */ - public static final String RULE_ROUTER_LABELS = "ruleRouter"; + public static final String ROUTER_LABELS = "allMetadata"; /** * transitive labels. */ - public static final String TRANSITIVE_LABELS = "transitive"; + public static final String TRANSITIVE_LABELS = "transitiveMetadata"; private Map> labels; @@ -53,10 +59,62 @@ public class PolarisRouterContext { return Collections.unmodifiableMap(subLabels); } - public void setLabels(String labelType, Map subLabels) { + public Map getLabels(String labelType, Set labelKeys) { + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + Map typeLabels = getLabels(labelType); + if (CollectionUtils.isEmpty(typeLabels)) { + return Collections.emptyMap(); + } + + Map labels = new HashMap<>(); + for (String key : labelKeys) { + String value = typeLabels.get(key); + if (StringUtils.isNotBlank(value)) { + labels.put(key, value); + } + } + return labels; + } + + public String getLabel(String labelKey) { + Map routerLabels = labels.get(ROUTER_LABELS); + if (CollectionUtils.isEmpty(routerLabels)) { + return StringUtils.EMPTY; + } + return routerLabels.get(labelKey); + } + + public Set getLabelAsSet(String labelKey) { + Map routerLabels = labels.get(ROUTER_LABELS); + if (CollectionUtils.isEmpty(routerLabels)) { + return Collections.emptySet(); + } + + for (Map.Entry entry : routerLabels.entrySet()) { + if (StringUtils.equalsIgnoreCase(labelKey, entry.getKey())) { + String keysStr = entry.getValue(); + if (StringUtils.isNotBlank(keysStr)) { + String[] keysArr = StringUtils.split(keysStr, ","); + return new HashSet<>(Arrays.asList(keysArr)); + } + } + } + + return Collections.emptySet(); + } + + public void putLabels(String labelType, Map subLabels) { if (this.labels == null) { this.labels = new HashMap<>(); } - labels.put(labelType, subLabels); + + Map subLabelMap = new LinkedCaseInsensitiveMap<>(); + if (!CollectionUtils.isEmpty(subLabels)) { + subLabelMap.putAll(subLabels); + } + labels.put(labelType, subLabelMap); } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplier.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplier.java index 1b8152f2a..6b5d14f26 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplier.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplier.java @@ -31,18 +31,14 @@ import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.pojo.PolarisServiceInstance; import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils; -import com.tencent.cloud.polaris.router.config.PolarisMetadataRouterProperties; -import com.tencent.cloud.polaris.router.config.PolarisNearByRouterProperties; -import com.tencent.cloud.polaris.router.config.PolarisRuleBasedRouterProperties; import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerRequest; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor; import com.tencent.polaris.api.exception.ErrorCode; import com.tencent.polaris.api.exception.PolarisException; import com.tencent.polaris.api.pojo.Instance; import com.tencent.polaris.api.pojo.ServiceInfo; import com.tencent.polaris.api.pojo.ServiceInstances; -import com.tencent.polaris.plugins.router.metadata.MetadataRouter; -import com.tencent.polaris.plugins.router.nearby.NearbyRouter; -import com.tencent.polaris.plugins.router.rule.RuleBasedRouter; import com.tencent.polaris.router.api.core.RouterAPI; import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse; @@ -73,21 +69,17 @@ import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; */ public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier { - private final PolarisNearByRouterProperties polarisNearByRouterProperties; - private final PolarisMetadataRouterProperties polarisMetadataRouterProperties; - private final PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties; private final RouterAPI routerAPI; + private final List requestInterceptors; + private final List responseInterceptors; public PolarisRouterServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, - RouterAPI routerAPI, - PolarisNearByRouterProperties polarisNearByRouterProperties, - PolarisMetadataRouterProperties polarisMetadataRouterProperties, - PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) { + RouterAPI routerAPI, List requestInterceptors, + List responseInterceptors) { super(delegate); this.routerAPI = routerAPI; - this.polarisNearByRouterProperties = polarisNearByRouterProperties; - this.polarisMetadataRouterProperties = polarisMetadataRouterProperties; - this.polarisRuleBasedRouterProperties = polarisRuleBasedRouterProperties; + this.requestInterceptors = requestInterceptors; + this.responseInterceptors = responseInterceptors; } @Override @@ -123,7 +115,7 @@ public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceI PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get() + routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get() .getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)); Map labelHeaderValuesMap = new HashMap<>(); @@ -134,18 +126,26 @@ public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceI catch (UnsupportedEncodingException e) { throw new RuntimeException("unsupported charset exception " + UTF_8); } - routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labelHeaderValuesMap); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labelHeaderValuesMap); return routerContext; } - Flux> doRouter(Flux> allServers, PolarisRouterContext key) { + Flux> doRouter(Flux> allServers, PolarisRouterContext routerContext) { ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(allServers); // filter instance by routers - ProcessRoutersRequest processRoutersRequest = buildProcessRoutersRequest(serviceInstances, key); + ProcessRoutersRequest processRoutersRequest = buildProcessRoutersRequest(serviceInstances, routerContext); + // process request interceptors + processRouterRequestInterceptors(processRoutersRequest, routerContext); + + // process router chain ProcessRoutersResponse processRoutersResponse = routerAPI.processRouters(processRoutersRequest); + // process response interceptors + processRouterResponseInterceptors(routerContext, processRoutersResponse); + + // transfer polaris server to ServiceInstance List filteredInstances = new ArrayList<>(); ServiceInstances filteredServiceInstances = processRoutersResponse.getServiceInstances(); for (Instance instance : filteredServiceInstances.getInstances()) { @@ -157,42 +157,25 @@ public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceI ProcessRoutersRequest buildProcessRoutersRequest(ServiceInstances serviceInstances, PolarisRouterContext key) { ProcessRoutersRequest processRoutersRequest = new ProcessRoutersRequest(); processRoutersRequest.setDstInstances(serviceInstances); - - // metadata router - if (polarisMetadataRouterProperties.isEnabled()) { - Map transitiveLabels = getRouterLabels(key, PolarisRouterContext.TRANSITIVE_LABELS); - processRoutersRequest.putRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, transitiveLabels); - } - - // nearby router - if (polarisNearByRouterProperties.isEnabled()) { - Map nearbyRouterMetadata = new HashMap<>(); - nearbyRouterMetadata.put(NearbyRouter.ROUTER_ENABLED, "true"); - processRoutersRequest.putRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY, nearbyRouterMetadata); - } - - // rule based router - // set dynamic switch for rule based router - boolean ruleBasedRouterEnabled = polarisRuleBasedRouterProperties.isEnabled(); - Map ruleRouterMetadata = new HashMap<>(); - ruleRouterMetadata.put(RuleBasedRouter.ROUTER_ENABLED, String.valueOf(ruleBasedRouterEnabled)); - processRoutersRequest.putRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED, ruleRouterMetadata); - ServiceInfo serviceInfo = new ServiceInfo(); serviceInfo.setNamespace(MetadataContext.LOCAL_NAMESPACE); serviceInfo.setService(MetadataContext.LOCAL_SERVICE); + processRoutersRequest.setSourceService(serviceInfo); + return processRoutersRequest; + } - if (ruleBasedRouterEnabled) { - Map ruleRouterLabels = getRouterLabels(key, PolarisRouterContext.RULE_ROUTER_LABELS); - // The label information that the rule based routing depends on - // is placed in the metadata of the source service for transmission. - // Later, can consider putting it in routerMetadata like other routers. - serviceInfo.setMetadata(ruleRouterLabels); + void processRouterRequestInterceptors(ProcessRoutersRequest processRoutersRequest, PolarisRouterContext routerContext) { + for (RouterRequestInterceptor requestInterceptor : requestInterceptors) { + requestInterceptor.apply(processRoutersRequest, routerContext); } + } - processRoutersRequest.setSourceService(serviceInfo); - - return processRoutersRequest; + private void processRouterResponseInterceptors(PolarisRouterContext routerContext, ProcessRoutersResponse processRoutersResponse) { + if (!CollectionUtils.isEmpty(responseInterceptors)) { + for (RouterResponseInterceptor responseInterceptor : responseInterceptors) { + responseInterceptor.apply(processRoutersResponse, routerContext); + } + } } private Map getRouterLabels(PolarisRouterContext key, String type) { diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterRuleLabelResolver.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterRuleLabelResolver.java index ded1784f4..f0df59de1 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterRuleLabelResolver.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterRuleLabelResolver.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils; import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.polaris.client.pb.ModelProto; import com.tencent.polaris.client.pb.RoutingProto; diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerBeanPostProcessor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/beanprocessor/LoadBalancerInterceptorBeanPostProcessor.java similarity index 83% rename from spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerBeanPostProcessor.java rename to spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/beanprocessor/LoadBalancerInterceptorBeanPostProcessor.java index 5bfe21415..fcca546dd 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerBeanPostProcessor.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/beanprocessor/LoadBalancerInterceptorBeanPostProcessor.java @@ -13,16 +13,16 @@ * 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.router.resttemplate; +package com.tencent.cloud.polaris.router.beanprocessor; import java.util.List; -import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.common.util.BeanFactoryUtils; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; +import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerInterceptor; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; import org.springframework.beans.BeansException; @@ -39,7 +39,7 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory; * *@author lepdou 2022-05-18 */ -public class PolarisLoadBalancerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { +public class LoadBalancerInterceptorBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { private BeanFactory factory; @@ -54,11 +54,11 @@ public class PolarisLoadBalancerBeanPostProcessor implements BeanPostProcessor, LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class); LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class); List routerLabelResolvers = BeanFactoryUtils.getBeans(factory, SpringWebRouterLabelResolver.class); - MetadataLocalProperties metadataLocalProperties = this.factory.getBean(MetadataLocalProperties.class); + StaticMetadataManager staticMetadataManager = this.factory.getBean(StaticMetadataManager.class); RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class); return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory, - routerLabelResolvers, metadataLocalProperties, routerRuleLabelResolver); + routerLabelResolvers, staticMetadataManager, routerRuleLabelResolver); } return bean; } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientBeanPostProcessor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/beanprocessor/ReactiveLoadBalancerClientFilterBeanPostProcessor.java similarity index 81% rename from spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientBeanPostProcessor.java rename to spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/beanprocessor/ReactiveLoadBalancerClientFilterBeanPostProcessor.java index f080d2aa3..d383901b5 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientBeanPostProcessor.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/beanprocessor/ReactiveLoadBalancerClientFilterBeanPostProcessor.java @@ -13,16 +13,16 @@ * 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.router.scg; +package com.tencent.cloud.polaris.router.beanprocessor; import java.util.List; -import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.common.util.BeanFactoryUtils; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; +import com.tencent.cloud.polaris.router.scg.PolarisReactiveLoadBalancerClientFilter; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; import org.springframework.beans.BeansException; @@ -39,7 +39,7 @@ import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; * * @author lepdou 2022-06-20 */ -public class PolarisLoadBalancerClientBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { +public class ReactiveLoadBalancerClientFilterBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { private BeanFactory factory; @@ -51,18 +51,19 @@ public class PolarisLoadBalancerClientBeanPostProcessor implements BeanPostProce @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // Support spring cloud gateway router. - // Replaces the default LoadBalancerClientFilter implementation and returns a custom PolarisLoadBalancerClientFilter + // Replaces the default ReactiveLoadBalancerClientFilter implementation + // and returns a custom PolarisReactiveLoadBalancerClientFilter if (bean instanceof ReactiveLoadBalancerClientFilter) { LoadBalancerClientFactory loadBalancerClientFactory = this.factory.getBean(LoadBalancerClientFactory.class); GatewayLoadBalancerProperties gatewayLoadBalancerProperties = this.factory.getBean(GatewayLoadBalancerProperties.class); LoadBalancerProperties loadBalancerProperties = this.factory.getBean(LoadBalancerProperties.class); List routerLabelResolvers = BeanFactoryUtils.getBeans(factory, SpringWebRouterLabelResolver.class); - MetadataLocalProperties metadataLocalProperties = this.factory.getBean(MetadataLocalProperties.class); + StaticMetadataManager staticMetadataManager = this.factory.getBean(StaticMetadataManager.class); RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class); return new PolarisReactiveLoadBalancerClientFilter( loadBalancerClientFactory, gatewayLoadBalancerProperties, loadBalancerProperties, - metadataLocalProperties, routerRuleLabelResolver, routerLabelResolvers); + staticMetadataManager, routerRuleLabelResolver, routerLabelResolvers); } return bean; } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/FeignAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/FeignAutoConfiguration.java index 81b720369..5775b6841 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/FeignAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/FeignAutoConfiguration.java @@ -20,7 +20,7 @@ package com.tencent.cloud.polaris.router.config; import java.util.List; -import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.feign.RouterLabelFeignInterceptor; import com.tencent.cloud.polaris.router.spi.FeignRouterLabelResolver; @@ -36,8 +36,8 @@ public class FeignAutoConfiguration { @Bean public RouterLabelFeignInterceptor routerLabelInterceptor(@Nullable List routerLabelResolvers, - MetadataLocalProperties metadataLocalProperties, + StaticMetadataManager staticMetadataManager, RouterRuleLabelResolver routerRuleLabelResolver) { - return new RouterLabelFeignInterceptor(routerLabelResolvers, metadataLocalProperties, routerRuleLabelResolver); + return new RouterLabelFeignInterceptor(routerLabelResolvers, staticMetadataManager, routerRuleLabelResolver); } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/LoadBalancerConfiguration.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/LoadBalancerConfiguration.java index 0205a74a3..8dc41be3a 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/LoadBalancerConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/LoadBalancerConfiguration.java @@ -18,7 +18,11 @@ package com.tencent.cloud.polaris.router.config; +import java.util.List; + import com.tencent.cloud.polaris.router.PolarisRouterServiceInstanceListSupplier; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor; import com.tencent.polaris.router.api.core.RouterAPI; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -56,16 +60,13 @@ public class LoadBalancerConfiguration { @ConditionalOnBean(ReactiveDiscoveryClient.class) public ServiceInstanceListSupplier polarisRouterDiscoveryClientServiceInstanceListSupplier( ConfigurableApplicationContext context, - RouterAPI routerAPI, - PolarisNearByRouterProperties polarisNearByRouterProperties, - PolarisMetadataRouterProperties polarisMetadataRouterProperties, - PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) { + RouterAPI routerAPI, List requestInterceptors, + List responseInterceptors) { return new PolarisRouterServiceInstanceListSupplier( ServiceInstanceListSupplier.builder().withDiscoveryClient().build(context), routerAPI, - polarisNearByRouterProperties, - polarisMetadataRouterProperties, - polarisRuleBasedRouterProperties); + requestInterceptors, + responseInterceptors); } } @@ -79,16 +80,13 @@ public class LoadBalancerConfiguration { @ConditionalOnBean(DiscoveryClient.class) public ServiceInstanceListSupplier polarisRouterDiscoveryClientServiceInstanceListSupplier( ConfigurableApplicationContext context, - RouterAPI routerAPI, - PolarisNearByRouterProperties polarisNearByRouterProperties, - PolarisMetadataRouterProperties polarisMetadataRouterProperties, - PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) { + RouterAPI routerAPI, List requestInterceptors, + List responseInterceptors) { return new PolarisRouterServiceInstanceListSupplier( ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().build(context), routerAPI, - polarisNearByRouterProperties, - polarisMetadataRouterProperties, - polarisRuleBasedRouterProperties); + requestInterceptors, + responseInterceptors); } } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java index 8b43239a8..8947e864f 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java @@ -20,10 +20,17 @@ package com.tencent.cloud.polaris.router.config; import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; -import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerBeanPostProcessor; -import com.tencent.cloud.polaris.router.scg.PolarisLoadBalancerClientBeanPostProcessor; +import com.tencent.cloud.polaris.router.beanprocessor.LoadBalancerInterceptorBeanPostProcessor; +import com.tencent.cloud.polaris.router.beanprocessor.ReactiveLoadBalancerClientFilterBeanPostProcessor; +import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties; +import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties; +import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties; +import com.tencent.cloud.polaris.router.interceptor.MetadataRouterRequestInterceptor; +import com.tencent.cloud.polaris.router.interceptor.NearbyRouterRequestInterceptor; +import com.tencent.cloud.polaris.router.interceptor.RuleBasedRouterRequestInterceptor; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -33,7 +40,7 @@ import org.springframework.core.annotation.Order; import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; /** - * router module auto configuration. + * configuration for router module singleton beans. * * @author lepdou 2022-05-11 */ @@ -45,19 +52,37 @@ public class RouterAutoConfiguration { @Bean @Order(HIGHEST_PRECEDENCE) @ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor") - public PolarisLoadBalancerBeanPostProcessor polarisLoadBalancerBeanPostProcessor() { - return new PolarisLoadBalancerBeanPostProcessor(); + public LoadBalancerInterceptorBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() { + return new LoadBalancerInterceptorBeanPostProcessor(); } @Bean @Order(HIGHEST_PRECEDENCE) @ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter") - public PolarisLoadBalancerClientBeanPostProcessor polarisLoadBalancerClientBeanPostProcessor() { - return new PolarisLoadBalancerClientBeanPostProcessor(); + public ReactiveLoadBalancerClientFilterBeanPostProcessor loadBalancerClientFilterBeanPostProcessor() { + return new ReactiveLoadBalancerClientFilterBeanPostProcessor(); } @Bean public RouterRuleLabelResolver routerRuleLabelResolver(ServiceRuleManager serviceRuleManager) { return new RouterRuleLabelResolver(serviceRuleManager); } + + @Bean + @ConditionalOnProperty(value = "spring.cloud.polaris.router.metadata-router.enabled", matchIfMissing = true) + public MetadataRouterRequestInterceptor metadataRouterRequestInterceptor(PolarisMetadataRouterProperties polarisMetadataRouterProperties) { + return new MetadataRouterRequestInterceptor(polarisMetadataRouterProperties); + } + + @Bean + @ConditionalOnProperty(value = "spring.cloud.polaris.router.nearby-router.enabled", matchIfMissing = true) + public NearbyRouterRequestInterceptor nearbyRouterRequestInterceptor(PolarisNearByRouterProperties polarisNearByRouterProperties) { + return new NearbyRouterRequestInterceptor(polarisNearByRouterProperties); + } + + @Bean + @ConditionalOnProperty(value = "spring.cloud.polaris.router.rule-router.enabled", matchIfMissing = true) + public RuleBasedRouterRequestInterceptor ruleBasedRouterRequestInterceptor(PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) { + return new RuleBasedRouterRequestInterceptor(polarisRuleBasedRouterProperties); + } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisMetadataRouterProperties.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/properties/PolarisMetadataRouterProperties.java similarity index 95% rename from spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisMetadataRouterProperties.java rename to spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/properties/PolarisMetadataRouterProperties.java index 2bf700c33..79f1f5aaa 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisMetadataRouterProperties.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/properties/PolarisMetadataRouterProperties.java @@ -13,10 +13,9 @@ * 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.router.config; +package com.tencent.cloud.polaris.router.config.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisNearByRouterProperties.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/properties/PolarisNearByRouterProperties.java similarity index 95% rename from spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisNearByRouterProperties.java rename to spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/properties/PolarisNearByRouterProperties.java index 3467b0587..0ca119564 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisNearByRouterProperties.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/properties/PolarisNearByRouterProperties.java @@ -13,10 +13,9 @@ * 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.router.config; +package com.tencent.cloud.polaris.router.config.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRuleBasedRouterProperties.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/properties/PolarisRuleBasedRouterProperties.java similarity index 95% rename from spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRuleBasedRouterProperties.java rename to spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/properties/PolarisRuleBasedRouterProperties.java index 8bd4a13cb..9ad4c8ef0 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRuleBasedRouterProperties.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/properties/PolarisRuleBasedRouterProperties.java @@ -13,10 +13,9 @@ * 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.router.config; +package com.tencent.cloud.polaris.router.config.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java index c3d19b35e..b040a6f7c 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java @@ -25,7 +25,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils; import feign.RequestTemplate; import org.apache.commons.lang.StringUtils; diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptor.java index 2fd2a856d..58f0dcab0 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptor.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptor.java @@ -29,7 +29,7 @@ import java.util.Set; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; -import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.polaris.router.RouterConstants; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; @@ -53,11 +53,11 @@ public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered private static final Logger LOGGER = LoggerFactory.getLogger(RouterLabelFeignInterceptor.class); private final List routerLabelResolvers; - private final MetadataLocalProperties metadataLocalProperties; + private final StaticMetadataManager staticMetadataManager; private final RouterRuleLabelResolver routerRuleLabelResolver; public RouterLabelFeignInterceptor(List routerLabelResolvers, - MetadataLocalProperties metadataLocalProperties, + StaticMetadataManager staticMetadataManager, RouterRuleLabelResolver routerRuleLabelResolver) { if (!CollectionUtils.isEmpty(routerLabelResolvers)) { routerLabelResolvers.sort(Comparator.comparingInt(Ordered::getOrder)); @@ -66,7 +66,7 @@ public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered else { this.routerLabelResolvers = null; } - this.metadataLocalProperties = metadataLocalProperties; + this.staticMetadataManager = staticMetadataManager; this.routerRuleLabelResolver = routerRuleLabelResolver; } @@ -78,7 +78,7 @@ public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered @Override public void apply(RequestTemplate requestTemplate) { // local service labels - Map labels = new HashMap<>(metadataLocalProperties.getContent()); + Map labels = new HashMap<>(staticMetadataManager.getMergedStaticMetadata()); // labels from rule expression String peerServiceName = requestTemplate.feignTarget().name(); @@ -109,11 +109,6 @@ public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered labels.putAll(transitiveLabels); // pass label by header - if (labels.size() == 0) { - requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER); - return; - } - String encodedLabelsContent; try { encodedLabelsContent = URLEncoder.encode(JacksonUtils.serialize2Json(labels), UTF_8); diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/MetadataRouterRequestInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/MetadataRouterRequestInterceptor.java new file mode 100644 index 000000000..9b9a78f2b --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/MetadataRouterRequestInterceptor.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.router.interceptor; + +import java.util.Map; +import java.util.Set; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.polaris.plugins.router.metadata.MetadataRouter; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; + +/** + * Router request interceptor for metadata router. + * @author lepdou 2022-07-06 + */ +public class MetadataRouterRequestInterceptor implements RouterRequestInterceptor { + private static final String LABEL_KEY_METADATA_ROUTER_KEYS = "system-metadata-router-keys"; + + private final PolarisMetadataRouterProperties polarisMetadataRouterProperties; + + public MetadataRouterRequestInterceptor(PolarisMetadataRouterProperties polarisMetadataRouterProperties) { + this.polarisMetadataRouterProperties = polarisMetadataRouterProperties; + } + + @Override + public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) { + if (!polarisMetadataRouterProperties.isEnabled()) { + return; + } + + // 1. get metadata router label keys + Set metadataRouterKeys = routerContext.getLabelAsSet(LABEL_KEY_METADATA_ROUTER_KEYS); + // 2. get metadata router labels + Map metadataRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS, + metadataRouterKeys); + // 3. set metadata router labels to request + request.addRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, metadataRouterLabels); + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/NearbyRouterRequestInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/NearbyRouterRequestInterceptor.java new file mode 100644 index 000000000..86efcd62e --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/NearbyRouterRequestInterceptor.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.router.interceptor; + +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.polaris.plugins.router.nearby.NearbyRouter; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; + +/** + * Router request interceptor for nearby router. + * @author lepdou 2022-07-06 + */ +public class NearbyRouterRequestInterceptor implements RouterRequestInterceptor { + + private final PolarisNearByRouterProperties polarisNearByRouterProperties; + + public NearbyRouterRequestInterceptor(PolarisNearByRouterProperties polarisNearByRouterProperties) { + this.polarisNearByRouterProperties = polarisNearByRouterProperties; + } + + @Override + public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) { + if (!polarisNearByRouterProperties.isEnabled()) { + return; + } + + Map nearbyRouterMetadata = new HashMap<>(); + nearbyRouterMetadata.put(NearbyRouter.ROUTER_ENABLED, "true"); + + request.addRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY, nearbyRouterMetadata); + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/RuleBasedRouterRequestInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/RuleBasedRouterRequestInterceptor.java new file mode 100644 index 000000000..9abba566f --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/RuleBasedRouterRequestInterceptor.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.router.interceptor; + +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.polaris.plugins.router.rule.RuleBasedRouter; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; + +/** + * Router request interceptor for rule based router. + * @author lepdou 2022-07-06 + */ +public class RuleBasedRouterRequestInterceptor implements RouterRequestInterceptor { + + private final PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties; + + public RuleBasedRouterRequestInterceptor(PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) { + this.polarisRuleBasedRouterProperties = polarisRuleBasedRouterProperties; + } + + @Override + public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) { + boolean ruleBasedRouterEnabled = polarisRuleBasedRouterProperties.isEnabled(); + + // set dynamic switch for rule based router + Map metadata = new HashMap<>(); + metadata.put(RuleBasedRouter.ROUTER_ENABLED, String.valueOf(ruleBasedRouterEnabled)); + request.addRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED, metadata); + + // The label information that the rule based routing depends on + // is placed in the metadata of the source service for transmission. + // Later, can consider putting it in routerMetadata like other routers. + if (ruleBasedRouterEnabled) { + Map ruleRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS); + request.getSourceService().setMetadata(ruleRouterLabels); + } + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptor.java index 275c1a712..848ff9054 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptor.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptor.java @@ -31,9 +31,9 @@ import java.util.Set; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; -import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; import com.tencent.cloud.polaris.router.RouterConstants; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; @@ -64,18 +64,18 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor { private final LoadBalancerClient loadBalancer; private final LoadBalancerRequestFactory requestFactory; private final List routerLabelResolvers; - private final MetadataLocalProperties metadataLocalProperties; + private final StaticMetadataManager staticMetadataManager; private final RouterRuleLabelResolver routerRuleLabelResolver; public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory, List routerLabelResolvers, - MetadataLocalProperties metadataLocalProperties, + StaticMetadataManager staticMetadataManager, RouterRuleLabelResolver routerRuleLabelResolver) { super(loadBalancer, requestFactory); this.loadBalancer = loadBalancer; this.requestFactory = requestFactory; - this.metadataLocalProperties = metadataLocalProperties; + this.staticMetadataManager = staticMetadataManager; this.routerRuleLabelResolver = routerRuleLabelResolver; if (!CollectionUtils.isEmpty(routerLabelResolvers)) { @@ -102,7 +102,7 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor { void setLabelsToHeaders(HttpRequest request, byte[] body, String peerServiceName) { // local service labels - Map labels = new HashMap<>(metadataLocalProperties.getContent()); + Map labels = new HashMap<>(staticMetadataManager.getMergedStaticMetadata()); // labels from rule expression Set expressionLabelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE, @@ -153,6 +153,6 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor { return Collections.emptyMap(); } - return ExpressionLabelUtils.resolve(request, labelKeys); + return SpringWebExpressionLabelUtils.resolve(request, labelKeys); } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilter.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilter.java index 22add0264..c3065381f 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilter.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilter.java @@ -29,9 +29,9 @@ import java.util.Set; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; -import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; import com.tencent.cloud.polaris.router.PolarisRouterServiceInstanceListSupplier; import com.tencent.cloud.polaris.router.RouterConstants; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; @@ -84,14 +84,14 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance private final LoadBalancerClientFactory clientFactory; private final GatewayLoadBalancerProperties gatewayLoadBalancerProperties; private final LoadBalancerProperties loadBalancerProperties; - private final MetadataLocalProperties metadataLocalProperties; + private final StaticMetadataManager staticMetadataManager; private final RouterRuleLabelResolver routerRuleLabelResolver; private final List routerLabelResolvers; public PolarisReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, GatewayLoadBalancerProperties gatewayLoadBalancerProperties, LoadBalancerProperties loadBalancerProperties, - MetadataLocalProperties metadataLocalProperties, + StaticMetadataManager staticMetadataManager, RouterRuleLabelResolver routerRuleLabelResolver, List routerLabelResolvers) { super(clientFactory, gatewayLoadBalancerProperties, loadBalancerProperties); @@ -99,7 +99,7 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance this.clientFactory = clientFactory; this.gatewayLoadBalancerProperties = gatewayLoadBalancerProperties; this.loadBalancerProperties = loadBalancerProperties; - this.metadataLocalProperties = metadataLocalProperties; + this.staticMetadataManager = staticMetadataManager; this.routerRuleLabelResolver = routerRuleLabelResolver; this.routerLabelResolvers = routerLabelResolvers; } @@ -223,7 +223,7 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance private Map genRouterLabels(ServerWebExchange exchange, String peerServiceName) { // local service labels - Map labels = new HashMap<>(metadataLocalProperties.getContent()); + Map labels = new HashMap<>(staticMetadataManager.getMergedStaticMetadata()); // labels from rule expression Set expressionLabelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE, @@ -262,6 +262,6 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance return Collections.emptyMap(); } - return ExpressionLabelUtils.resolve(exchange, labelKeys); + return SpringWebExpressionLabelUtils.resolve(exchange, labelKeys); } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterRequestInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterRequestInterceptor.java new file mode 100644 index 000000000..6e515d9c2 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterRequestInterceptor.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.router.spi; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; + +/** + * The interceptor for router request. Router plugin can modify request by interceptor. + * + * @author lepdou 2022-07-11 + */ +public interface RouterRequestInterceptor { + + /** + * processing request. + * @param request the router request. + * @param routerContext the router context. + */ + void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext); +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterResponseInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterResponseInterceptor.java new file mode 100644 index 000000000..5d80d0c25 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterResponseInterceptor.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.router.spi; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse; + +/** + * The interceptor for router response. Router plugin can modify router response by interceptor. + * + * @author lepdou 2022-07-11 + */ +public interface RouterResponseInterceptor { + + /** + * processing router response. + * + * @param response the router response. + * @param routerContext the router context. + */ + void apply(ProcessRoutersResponse response, PolarisRouterContext routerContext); +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java index 0f2094233..ced54a597 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java @@ -20,7 +20,9 @@ package com.tencent.cloud.polaris.router; import java.util.HashMap; import java.util.Map; +import java.util.Set; +import com.google.common.collect.Sets; import org.junit.Assert; import org.junit.Test; @@ -38,27 +40,76 @@ public class PolarisRouterContextTest { labels.put("k2", "v2"); PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size()); - Assert.assertEquals(2, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size()); - Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k1")); - Assert.assertEquals("v2", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k2")); - Assert.assertNull(routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k3")); + Assert.assertEquals(2, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size()); + Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k1")); + Assert.assertEquals("v2", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k2")); + Assert.assertNull(routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k3")); } @Test public void testSetNull() { PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, null); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, null); Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size()); - Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size()); + Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size()); } @Test public void testGetEmptyRouterContext() { PolarisRouterContext routerContext = new PolarisRouterContext(); Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size()); - Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size()); + Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size()); + } + + @Test + public void testGetLabelByKeys() { + Map labels = new HashMap<>(); + labels.put("k1", "v1"); + labels.put("k2", "v2"); + labels.put("k3", "v3"); + + PolarisRouterContext routerContext = new PolarisRouterContext(); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + + Map resolvedLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS, + Sets.newHashSet("k1", "k2", "k4")); + + Assert.assertEquals(2, resolvedLabels.size()); + Assert.assertEquals("v1", resolvedLabels.get("k1")); + Assert.assertEquals("v2", resolvedLabels.get("k2")); + } + + @Test + public void testGetLabel() { + Map labels = new HashMap<>(); + labels.put("k1", "v1"); + labels.put("k2", "v2"); + labels.put("k3", "v3"); + + PolarisRouterContext routerContext = new PolarisRouterContext(); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + + String resolvedLabel = routerContext.getLabel("k1"); + + Assert.assertEquals("v1", resolvedLabel); + } + + @Test + public void testGetLabelAsSet() { + Map labels = new HashMap<>(); + labels.put("k1", "v1,v2,v3"); + + PolarisRouterContext routerContext = new PolarisRouterContext(); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + + Set resolvedLabels = routerContext.getLabelAsSet("k1"); + + Assert.assertEquals(3, resolvedLabels.size()); + Assert.assertTrue(resolvedLabels.contains("v1")); + Assert.assertTrue(resolvedLabels.contains("v2")); + Assert.assertTrue(resolvedLabels.contains("v3")); } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplierTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplierTest.java index afe003b76..effd25093 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplierTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplierTest.java @@ -31,9 +31,13 @@ import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.pojo.PolarisServiceInstance; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties; -import com.tencent.cloud.polaris.router.config.PolarisMetadataRouterProperties; -import com.tencent.cloud.polaris.router.config.PolarisNearByRouterProperties; -import com.tencent.cloud.polaris.router.config.PolarisRuleBasedRouterProperties; +import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties; +import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties; +import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties; +import com.tencent.cloud.polaris.router.interceptor.MetadataRouterRequestInterceptor; +import com.tencent.cloud.polaris.router.interceptor.NearbyRouterRequestInterceptor; +import com.tencent.cloud.polaris.router.interceptor.RuleBasedRouterRequestInterceptor; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; import com.tencent.polaris.api.pojo.DefaultInstance; import com.tencent.polaris.api.pojo.DefaultServiceInstances; import com.tencent.polaris.api.pojo.Instance; @@ -46,6 +50,7 @@ import com.tencent.polaris.router.api.core.RouterAPI; import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -73,6 +78,7 @@ public class PolarisRouterServiceInstanceListSupplierTest { private final String testNamespace = "testNamespace"; private final String testCallerService = "testCallerService"; private final String testCalleeService = "testCalleeService"; + private final List requestInterceptors = new ArrayList<>(); @Mock private ServiceInstanceListSupplier delegate; @Mock @@ -86,6 +92,13 @@ public class PolarisRouterServiceInstanceListSupplierTest { @Mock private RouterAPI routerAPI; + @Before + public void before() { + requestInterceptors.add(new MetadataRouterRequestInterceptor(polarisMetadataRouterProperties)); + requestInterceptors.add(new NearbyRouterRequestInterceptor(polarisNearByRouterProperties)); + requestInterceptors.add(new RuleBasedRouterRequestInterceptor(polarisRuleBasedRouterProperties)); + } + @Test public void testBuildMetadataRouteRequest() { when(polarisMetadataRouterProperties.isEnabled()).thenReturn(true); @@ -96,14 +109,19 @@ public class PolarisRouterServiceInstanceListSupplierTest { setTransitiveMetadata(); - PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier( - delegate, routerAPI, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties); + PolarisRouterServiceInstanceListSupplier polarisSupplier = new PolarisRouterServiceInstanceListSupplier( + delegate, routerAPI, requestInterceptors, null); ServiceInstances serviceInstances = assembleServiceInstances(); PolarisRouterContext routerContext = assembleRouterContext(); - ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext); + Map oldRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS); + Map newRouterLabels = new HashMap<>(oldRouterLabels); + newRouterLabels.put("system-metadata-router-keys", "k2"); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, newRouterLabels); + + ProcessRoutersRequest request = polarisSupplier.buildProcessRoutersRequest(serviceInstances, routerContext); + polarisSupplier.processRouterRequestInterceptors(request, routerContext); Map routerMetadata = request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA); @@ -125,14 +143,14 @@ public class PolarisRouterServiceInstanceListSupplierTest { setTransitiveMetadata(); - PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier( - delegate, routerAPI, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties); + PolarisRouterServiceInstanceListSupplier polarisSupplier = new PolarisRouterServiceInstanceListSupplier( + delegate, routerAPI, requestInterceptors, null); ServiceInstances serviceInstances = assembleServiceInstances(); PolarisRouterContext routerContext = assembleRouterContext(); - ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext); + ProcessRoutersRequest request = polarisSupplier.buildProcessRoutersRequest(serviceInstances, routerContext); + polarisSupplier.processRouterRequestInterceptors(request, routerContext); Map routerMetadata = request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY); @@ -155,14 +173,14 @@ public class PolarisRouterServiceInstanceListSupplierTest { setTransitiveMetadata(); - PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier( - delegate, routerAPI, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties); + PolarisRouterServiceInstanceListSupplier polarisSupplier = new PolarisRouterServiceInstanceListSupplier( + delegate, routerAPI, requestInterceptors, null); ServiceInstances serviceInstances = assembleServiceInstances(); PolarisRouterContext routerContext = assembleRouterContext(); - ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext); + ProcessRoutersRequest request = polarisSupplier.buildProcessRoutersRequest(serviceInstances, routerContext); + polarisSupplier.processRouterRequestInterceptors(request, routerContext); Map routerMetadata = request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED); @@ -185,14 +203,13 @@ public class PolarisRouterServiceInstanceListSupplierTest { setTransitiveMetadata(); - PolarisRouterServiceInstanceListSupplier compositeRule = new PolarisRouterServiceInstanceListSupplier( - delegate, routerAPI, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties); + PolarisRouterServiceInstanceListSupplier polarisSupplier = new PolarisRouterServiceInstanceListSupplier( + delegate, routerAPI, requestInterceptors, null); ProcessRoutersResponse assembleResponse = assembleProcessRoutersResponse(); when(routerAPI.processRouters(any())).thenReturn(assembleResponse); - Flux> servers = compositeRule.doRouter(assembleServers(), assembleRouterContext()); + Flux> servers = polarisSupplier.doRouter(assembleServers(), assembleRouterContext()); Assert.assertEquals(assembleResponse.getServiceInstances().getInstances().size(), @@ -229,8 +246,8 @@ public class PolarisRouterServiceInstanceListSupplierTest { Map routerLabels = new HashMap<>(); routerLabels.put("k2", "v2"); routerLabels.put("k3", "v3"); - routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); - routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, routerLabels); + routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, routerLabels); return routerContext; } diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerBeanPostProcessorTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java similarity index 81% rename from spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerBeanPostProcessorTest.java rename to spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java index f173b67c8..057759af1 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerBeanPostProcessorTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java @@ -13,14 +13,14 @@ * 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.router.resttemplate; +package com.tencent.cloud.polaris.router.beanprocessor; -import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.common.util.BeanFactoryUtils; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; +import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerInterceptor; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; import org.junit.Assert; import org.junit.Test; @@ -38,19 +38,19 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory; import static org.mockito.Mockito.when; /** - * Test for ${@link PolarisLoadBalancerBeanPostProcessor}. + * Test for ${@link LoadBalancerInterceptorBeanPostProcessor}. * * @author lepdou 2022-05-26 */ @RunWith(MockitoJUnitRunner.class) -public class PolarisLoadBalancerBeanPostProcessorTest { +public class LoadBalancerInterceptorBeanPostProcessorTest { @Mock private LoadBalancerClient loadBalancerClient; @Mock private LoadBalancerRequestFactory loadBalancerRequestFactory; @Mock - private MetadataLocalProperties metadataLocalProperties; + private StaticMetadataManager staticMetadataManager; @Mock private RouterRuleLabelResolver routerRuleLabelResolver; @Mock @@ -60,7 +60,7 @@ public class PolarisLoadBalancerBeanPostProcessorTest { public void testWrapperLoadBalancerInterceptor() { when(beanFactory.getBean(LoadBalancerRequestFactory.class)).thenReturn(loadBalancerRequestFactory); when(beanFactory.getBean(LoadBalancerClient.class)).thenReturn(loadBalancerClient); - when(beanFactory.getBean(MetadataLocalProperties.class)).thenReturn(metadataLocalProperties); + when(beanFactory.getBean(StaticMetadataManager.class)).thenReturn(staticMetadataManager); when(beanFactory.getBean(RouterRuleLabelResolver.class)).thenReturn(routerRuleLabelResolver); try (MockedStatic mockedBeanFactoryUtils = Mockito.mockStatic(BeanFactoryUtils.class)) { @@ -68,7 +68,7 @@ public class PolarisLoadBalancerBeanPostProcessorTest { .thenReturn(null); LoadBalancerInterceptor loadBalancerInterceptor = new LoadBalancerInterceptor(loadBalancerClient, loadBalancerRequestFactory); - PolarisLoadBalancerBeanPostProcessor processor = new PolarisLoadBalancerBeanPostProcessor(); + LoadBalancerInterceptorBeanPostProcessor processor = new LoadBalancerInterceptorBeanPostProcessor(); processor.setBeanFactory(beanFactory); Object bean = processor.postProcessBeforeInitialization(loadBalancerInterceptor, ""); @@ -79,7 +79,7 @@ public class PolarisLoadBalancerBeanPostProcessorTest { @Test public void testNotWrapperLoadBalancerInterceptor() { - PolarisLoadBalancerBeanPostProcessor processor = new PolarisLoadBalancerBeanPostProcessor(); + LoadBalancerInterceptorBeanPostProcessor processor = new LoadBalancerInterceptorBeanPostProcessor(); processor.setBeanFactory(beanFactory); OtherBean otherBean = new OtherBean(); diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientBeanPostProcessorTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/beanprocessor/ReactiveLoadBalancerClientFilterBeanPostProcessorTest.java similarity index 82% rename from spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientBeanPostProcessorTest.java rename to spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/beanprocessor/ReactiveLoadBalancerClientFilterBeanPostProcessorTest.java index e354ab235..883cb779d 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientBeanPostProcessorTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/beanprocessor/ReactiveLoadBalancerClientFilterBeanPostProcessorTest.java @@ -13,14 +13,14 @@ * 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.router.scg; +package com.tencent.cloud.polaris.router.beanprocessor; -import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.common.util.BeanFactoryUtils; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; +import com.tencent.cloud.polaris.router.scg.PolarisReactiveLoadBalancerClientFilter; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; import org.junit.Assert; import org.junit.Test; @@ -40,12 +40,12 @@ import static org.mockito.Mockito.when; /** - * Test for ${@link PolarisLoadBalancerClientBeanPostProcessor}. + * Test for ${@link ReactiveLoadBalancerClientFilterBeanPostProcessor}. * * @author lepdou 2022-07-04 */ @RunWith(MockitoJUnitRunner.class) -public class PolarisLoadBalancerClientBeanPostProcessorTest { +public class ReactiveLoadBalancerClientFilterBeanPostProcessorTest { @Mock private BeanFactory beanFactory; @@ -56,7 +56,7 @@ public class PolarisLoadBalancerClientBeanPostProcessorTest { @Mock private LoadBalancerProperties loadBalancerProperties; @Mock - private MetadataLocalProperties metadataLocalProperties; + private StaticMetadataManager staticMetadataManager; @Mock private RouterRuleLabelResolver routerRuleLabelResolver; @@ -65,7 +65,7 @@ public class PolarisLoadBalancerClientBeanPostProcessorTest { when(beanFactory.getBean(LoadBalancerClientFactory.class)).thenReturn(loadBalancerClientFactory); when(beanFactory.getBean(GatewayLoadBalancerProperties.class)).thenReturn(gatewayLoadBalancerProperties); when(beanFactory.getBean(LoadBalancerProperties.class)).thenReturn(loadBalancerProperties); - when(beanFactory.getBean(MetadataLocalProperties.class)).thenReturn(metadataLocalProperties); + when(beanFactory.getBean(StaticMetadataManager.class)).thenReturn(staticMetadataManager); when(beanFactory.getBean(RouterRuleLabelResolver.class)).thenReturn(routerRuleLabelResolver); try (MockedStatic mockedBeanFactoryUtils = Mockito.mockStatic(BeanFactoryUtils.class)) { @@ -75,7 +75,7 @@ public class PolarisLoadBalancerClientBeanPostProcessorTest { ReactiveLoadBalancerClientFilter reactiveLoadBalancerClientFilter = new ReactiveLoadBalancerClientFilter( loadBalancerClientFactory, gatewayLoadBalancerProperties, loadBalancerProperties); - PolarisLoadBalancerClientBeanPostProcessor processor = new PolarisLoadBalancerClientBeanPostProcessor(); + ReactiveLoadBalancerClientFilterBeanPostProcessor processor = new ReactiveLoadBalancerClientFilterBeanPostProcessor(); processor.setBeanFactory(beanFactory); Object bean = processor.postProcessBeforeInitialization(reactiveLoadBalancerClientFilter, ""); @@ -86,7 +86,7 @@ public class PolarisLoadBalancerClientBeanPostProcessorTest { @Test public void testNotWrapLoadBalancerInterceptor() { - PolarisLoadBalancerClientBeanPostProcessor processor = new PolarisLoadBalancerClientBeanPostProcessor(); + ReactiveLoadBalancerClientFilterBeanPostProcessor processor = new ReactiveLoadBalancerClientFilterBeanPostProcessor(); processor.setBeanFactory(beanFactory); OtherBean otherBean = new OtherBean(); diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/endpoint/PolarisRouterEndpointTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/endpoint/PolarisRouterEndpointTest.java index 27183c2a5..e400013c1 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/endpoint/PolarisRouterEndpointTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/endpoint/PolarisRouterEndpointTest.java @@ -42,7 +42,8 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; /** - * Test for {@link PolarisRouterEndpoint} + * Test for {@link PolarisRouterEndpoint}. + * * @author lepdou 2022-07-25 */ @RunWith(MockitoJUnitRunner.class) diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptorTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptorTest.java index 01824e50d..408fd220f 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptorTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptorTest.java @@ -29,7 +29,7 @@ import java.util.Set; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; -import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.polaris.router.RouterConstants; @@ -58,7 +58,7 @@ import static org.mockito.Mockito.when; public class RouterLabelFeignInterceptorTest { @Mock - private MetadataLocalProperties metadataLocalProperties; + private StaticMetadataManager staticMetadataManager; @Mock private RouterRuleLabelResolver routerRuleLabelResolver; @Mock @@ -68,7 +68,7 @@ public class RouterLabelFeignInterceptorTest { public void testResolveRouterLabel() { RouterLabelFeignInterceptor routerLabelFeignInterceptor = new RouterLabelFeignInterceptor( Collections.singletonList(routerLabelResolver), - metadataLocalProperties, routerRuleLabelResolver); + staticMetadataManager, routerRuleLabelResolver); // mock request template RequestTemplate requestTemplate = new RequestTemplate(); @@ -113,7 +113,7 @@ public class RouterLabelFeignInterceptorTest { Map localMetadata = new HashMap<>(); localMetadata.put("k3", "v31"); localMetadata.put("k4", "v4"); - when(metadataLocalProperties.getContent()).thenReturn(localMetadata); + when(staticMetadataManager.getMergedStaticMetadata()).thenReturn(localMetadata); routerLabelFeignInterceptor.apply(requestTemplate); diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptorTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptorTest.java index 78a9440e1..cb0939e5a 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptorTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptorTest.java @@ -28,7 +28,7 @@ import java.util.Set; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; -import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.polaris.router.RouterConstants; @@ -75,7 +75,7 @@ public class PolarisLoadBalancerInterceptorTest { @Mock private SpringWebRouterLabelResolver routerLabelResolver; @Mock - private MetadataLocalProperties metadataLocalProperties; + private StaticMetadataManager staticMetadataManager; @Mock private RouterRuleLabelResolver routerRuleLabelResolver; @@ -104,7 +104,7 @@ public class PolarisLoadBalancerInterceptorTest { Map localMetadata = new HashMap<>(); localMetadata.put("k1", "v1"); localMetadata.put("k2", "v2"); - when(metadataLocalProperties.getContent()).thenReturn(localMetadata); + when(staticMetadataManager.getMergedStaticMetadata()).thenReturn(localMetadata); // mock expression rule labels @@ -133,11 +133,11 @@ public class PolarisLoadBalancerInterceptorTest { when(loadBalancerRequestFactory.createRequest(request, null, null)).thenReturn(loadBalancerRequest); PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(loadBalancerClient, - loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), metadataLocalProperties, routerRuleLabelResolver); + loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), staticMetadataManager, routerRuleLabelResolver); polarisLoadBalancerInterceptor.intercept(request, null, null); - verify(metadataLocalProperties).getContent(); + verify(staticMetadataManager).getMergedStaticMetadata(); verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService); verify(routerLabelResolver).resolve(request, null, expressionKeys); } @@ -152,7 +152,7 @@ public class PolarisLoadBalancerInterceptorTest { Map localMetadata = new HashMap<>(); localMetadata.put("k1", "v1"); localMetadata.put("k2", "v2"); - when(metadataLocalProperties.getContent()).thenReturn(localMetadata); + when(staticMetadataManager.getMergedStaticMetadata()).thenReturn(localMetadata); // mock expression rule labels @@ -178,11 +178,11 @@ public class PolarisLoadBalancerInterceptorTest { mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext); PolarisLoadBalancerInterceptor polarisLoadBalancerInterceptor = new PolarisLoadBalancerInterceptor(loadBalancerClient, - loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), metadataLocalProperties, routerRuleLabelResolver); + loadBalancerRequestFactory, Collections.singletonList(routerLabelResolver), staticMetadataManager, routerRuleLabelResolver); polarisLoadBalancerInterceptor.setLabelsToHeaders(request, null, calleeService); - verify(metadataLocalProperties).getContent(); + verify(staticMetadataManager).getMergedStaticMetadata(); verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService); verify(routerLabelResolver).resolve(request, null, expressionKeys); diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilterTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilterTest.java index 81d8150f3..7b9176fe8 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilterTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilterTest.java @@ -29,7 +29,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; -import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.polaris.router.RouterConstants; @@ -71,7 +71,7 @@ public class PolarisReactiveLoadBalancerClientFilterTest { private static MockedStatic mockedMetadataContextHolder; @Mock - private MetadataLocalProperties metadataLocalProperties; + private StaticMetadataManager staticMetadataManager; @Mock private SpringWebRouterLabelResolver routerLabelResolver; @Mock @@ -110,12 +110,12 @@ public class PolarisReactiveLoadBalancerClientFilterTest { @Test public void testGenRouterHttpHeaders() throws UnsupportedEncodingException { PolarisReactiveLoadBalancerClientFilter filter = new PolarisReactiveLoadBalancerClientFilter(loadBalancerClientFactory, - gatewayLoadBalancerProperties, loadBalancerProperties, metadataLocalProperties, routerRuleLabelResolver, + gatewayLoadBalancerProperties, loadBalancerProperties, staticMetadataManager, routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver)); Map localMetadata = new HashMap<>(); localMetadata.put("env", "blue"); - when(metadataLocalProperties.getContent()).thenReturn(localMetadata); + when(staticMetadataManager.getMergedStaticMetadata()).thenReturn(localMetadata); Set expressionLabelKeys = Sets.newHashSet("${http.header.k1}", "${http.query.userid}"); when(routerRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString(), anyString())).thenReturn(expressionLabelKeys); 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 5e451f3de..1a6cb57d6 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 @@ -27,11 +27,26 @@ import org.springframework.core.Ordered; public final class MetadataConstant { /** - * Default Private Constructor. + * sct transitive header prefix. */ + public static final String SCT_TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-"; + /** + * sct transitive header prefix length. + */ + public static final int SCT_TRANSITIVE_HEADER_PREFIX_LENGTH = SCT_TRANSITIVE_HEADER_PREFIX.length(); + + /** + * 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, ... */ 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 9874b77d3..060bed30d 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 @@ -19,11 +19,8 @@ package com.tencent.cloud.common.metadata.config; import com.tencent.cloud.common.metadata.StaticMetadataManager; -import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter; import com.tencent.cloud.common.spi.InstanceMetadataProvider; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.lang.Nullable; @@ -51,16 +48,4 @@ public class MetadataAutoConfiguration { return new StaticMetadataManager(metadataLocalProperties, instanceMetadataProvider); } - /** - * Create when gateway application is SCG. - */ - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.GlobalFilter") - protected static class MetadataScgFilterConfig { - - @Bean - public GlobalFilter metadataFirstScgFilter() { - return new MetadataFirstScgFilter(); - } - } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/filter/gateway/MetadataFirstScgFilter.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/filter/gateway/MetadataFirstScgFilter.java deleted file mode 100644 index 6cc17a9c1..000000000 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/filter/gateway/MetadataFirstScgFilter.java +++ /dev/null @@ -1,64 +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.common.metadata.filter.gateway; - -import com.tencent.cloud.common.constant.MetadataConstant; -import com.tencent.cloud.common.metadata.MetadataContext; -import com.tencent.cloud.common.metadata.MetadataContextHolder; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.core.Ordered; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER; - -/** - * Scg output first filter used for setting peer info in context. - * - * @author Haotian Zhang - */ -public class MetadataFirstScgFilter implements GlobalFilter, Ordered { - - /** - * Order of MetadataFirstScgFilter. - */ - public static final int METADATA_FIRST_FILTER_ORDER = ROUTE_TO_URL_FILTER_ORDER + 1; - - @Override - public int getOrder() { - return METADATA_FIRST_FILTER_ORDER; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // get metadata of current thread - MetadataContext metadataContext = exchange - .getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT); - if (metadataContext == null) { - metadataContext = MetadataContextHolder.get(); - } - - exchange.getAttributes().put(MetadataConstant.HeaderName.METADATA_CONTEXT, - metadataContext); - - return chain.filter(exchange); - } -} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Condition.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Condition.java new file mode 100644 index 000000000..6823594ed --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Condition.java @@ -0,0 +1,65 @@ +/* + * 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.rule; + +import java.util.List; + +/** + * Condition expression. + * @author lepdou 2022-07-06 + */ +public class Condition { + + private String key; + private String operation; + private List values; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + @Override + public String toString() { + return "Condition{" + + "key='" + key + '\'' + + ", values='" + values + '\'' + + ", operation='" + operation + '\'' + + '}'; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/ConditionUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/ConditionUtils.java new file mode 100644 index 000000000..6092c0516 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/ConditionUtils.java @@ -0,0 +1,48 @@ +/* + * 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.rule; + +import java.util.List; +import java.util.Map; + +/** + * The util for condition expression. + * @author lepdou 2022-07-11 + */ +public final class ConditionUtils { + + private ConditionUtils() { + } + + public static boolean match(Map actualValues, List conditions) { + boolean allMatched = true; + for (Condition condition : conditions) { + List expectedValues = condition.getValues(); + String operation = condition.getOperation(); + String key = condition.getKey(); + String actualValue = actualValues.get(key); + + if (!Operation.match(expectedValues, actualValue, operation)) { + allMatched = false; + break; + } + } + return allMatched; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPair.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPair.java new file mode 100644 index 000000000..cd722fa15 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPair.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.common.rule; + +/** + * Key/value pair. + * @author lepdou 2022-07-06 + */ +public class KVPair { + + private String key; + private String value; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + + @Override + public String toString() { + return "KVPair{" + + "key='" + key + '\'' + + ", value='" + value + '\'' + + '}'; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPairUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPairUtils.java new file mode 100644 index 000000000..11edcefd8 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPairUtils.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.common.rule; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.util.CollectionUtils; + +/** + * The util for key/value pair. + * @author lepdou 2022-07-11 + */ +public final class KVPairUtils { + + private KVPairUtils() { + } + + public static Map toMap(List labels) { + if (CollectionUtils.isEmpty(labels)) { + return Collections.emptyMap(); + } + + Map result = new HashMap<>(); + labels.forEach(label -> { + result.put(label.getKey(), label.getValue()); + }); + + return result; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Operation.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Operation.java new file mode 100644 index 000000000..f48659c57 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Operation.java @@ -0,0 +1,133 @@ +/* + * 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.rule; + +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.commons.lang.StringUtils; + +import org.springframework.util.CollectionUtils; + +/** + * The condition operation. + * @author lepdou 2022-07-11 + */ +public enum Operation { + + /** + * case sensitive string equals. + */ + EQUALS("EQUALS"), + /** + * case sensitive string not equals. + */ + NOT_EQUALS("NOT_EQUALS"), + /** + * whether element in collection. + */ + IN("IN"), + /** + * whether element not in collection. + */ + NOT_IN("NOT_IN"), + /** + * regex operation. + */ + REGEX("REGEX"), + /** + * whether element is blank. + */ + BLANK("BLANK"), + /** + * whether element is not blank. + */ + NOT_BLANK("NOT_BLANK"); + + private final String value; + + Operation(String value) { + this.value = value; + } + + public static boolean match(List expectedValues, String actualValue, String rawOperation) { + String firstExpectedValue = null; + if (!CollectionUtils.isEmpty(expectedValues)) { + firstExpectedValue = expectedValues.get(0); + } + + switch (getOperation(rawOperation)) { + case EQUALS: + return firstExpectedValue != null && StringUtils.equals(actualValue, firstExpectedValue); + case NOT_EQUALS: + return firstExpectedValue == null || !StringUtils.equals(actualValue, firstExpectedValue); + case BLANK: + return StringUtils.isBlank(actualValue); + case NOT_BLANK: + return !StringUtils.isBlank(actualValue); + case IN: + if (CollectionUtils.isEmpty(expectedValues)) { + return false; + } + return expectedValues.contains(actualValue); + case NOT_IN: + if (CollectionUtils.isEmpty(expectedValues)) { + return true; + } + return !expectedValues.contains(actualValue); + case REGEX: + if (firstExpectedValue == null) { + return false; + } + Pattern r = Pattern.compile(firstExpectedValue); + return r.matcher(actualValue).matches(); + default: + return false; + } + } + + public static Operation getOperation(String operation) { + if (StringUtils.equalsIgnoreCase(operation, EQUALS.value)) { + return EQUALS; + } + if (StringUtils.equalsIgnoreCase(operation, NOT_EQUALS.value)) { + return NOT_EQUALS; + } + if (StringUtils.equalsIgnoreCase(operation, IN.value)) { + return IN; + } + if (StringUtils.equalsIgnoreCase(operation, NOT_IN.value)) { + return NOT_IN; + } + if (StringUtils.equalsIgnoreCase(operation, REGEX.value)) { + return REGEX; + } + if (StringUtils.equalsIgnoreCase(operation, BLANK.value)) { + return BLANK; + } + if (StringUtils.equalsIgnoreCase(operation, NOT_BLANK.value)) { + return NOT_BLANK; + } + throw new RuntimeException("Unsupported operation. operation = " + operation); + } + + public String getValue() { + return value; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java deleted file mode 100644 index 93cd9a1f7..000000000 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java +++ /dev/null @@ -1,314 +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.common.util; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang.StringUtils; - -import org.springframework.http.HttpCookie; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpRequest; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.CollectionUtils; -import org.springframework.util.MultiValueMap; -import org.springframework.web.server.ServerWebExchange; - -/** - * the utils for parse label expression. - * - * @author lepdou, cheese8 - */ -public final class ExpressionLabelUtils { - /** - * the prefix of expression. - */ - public static final String LABEL_PREFIX = "${"; - /** - * the expression prefix of header label. - */ - public static final String LABEL_HEADER_PREFIX = "${http.header."; - /** - * the length of expression header label prefix. - */ - public static final int LABEL_HEADER_PREFIX_LEN = LABEL_HEADER_PREFIX.length(); - /** - * the expression prefix of query. - */ - public static final String LABEL_QUERY_PREFIX = "${http.query."; - /** - * the length of expression query label prefix. - */ - public static final int LABEL_QUERY_PREFIX_LEN = LABEL_QUERY_PREFIX.length(); - /** - * the expression prefix of cookie. - */ - public static final String LABEL_COOKIE_PREFIX = "${http.cookie."; - /** - * the length of expression cookie label prefix. - */ - public static final int LABEL_COOKIE_PREFIX_LEN = LABEL_COOKIE_PREFIX.length(); - /** - * the expression of method. - */ - public static final String LABEL_METHOD = "${http.method}"; - /** - * the expression of uri. - */ - public static final String LABEL_URI = "${http.uri}"; - /** - * the suffix of expression. - */ - public static final String LABEL_SUFFIX = "}"; - private ExpressionLabelUtils() { - } - - public static boolean isExpressionLabel(String labelKey) { - if (StringUtils.isEmpty(labelKey)) { - return false; - } - return StringUtils.startsWith(labelKey, LABEL_PREFIX) && StringUtils.endsWith(labelKey, LABEL_SUFFIX); - } - - public static Map resolve(HttpServletRequest request, Set labelKeys) { - if (CollectionUtils.isEmpty(labelKeys)) { - return Collections.emptyMap(); - } - - Map labels = new HashMap<>(); - - for (String labelKey : labelKeys) { - if (!isExpressionLabel(labelKey)) { - continue; - } - if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) { - String headerKey = parseHeaderKey(labelKey); - if (StringUtils.isBlank(headerKey)) { - continue; - } - labels.put(labelKey, request.getHeader(headerKey)); - } - else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) { - String queryKey = parseQueryKey(labelKey); - if (StringUtils.isBlank(queryKey)) { - continue; - } - labels.put(labelKey, getQueryValue(request.getQueryString(), queryKey)); - } - else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) { - String cookieKey = parseCookieKey(labelKey); - if (StringUtils.isBlank(cookieKey)) { - continue; - } - labels.put(labelKey, getCookieValue(request.getCookies(), cookieKey)); - } - else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) { - labels.put(labelKey, request.getMethod()); - } - else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) { - labels.put(labelKey, request.getRequestURI()); - } - } - - return labels; - } - - public static Map resolve(ServerWebExchange exchange, Set labelKeys) { - if (CollectionUtils.isEmpty(labelKeys)) { - return Collections.emptyMap(); - } - - Map labels = new HashMap<>(); - - for (String labelKey : labelKeys) { - if (!isExpressionLabel(labelKey)) { - continue; - } - if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) { - String headerKey = parseHeaderKey(labelKey); - if (StringUtils.isBlank(headerKey)) { - continue; - } - labels.put(labelKey, getHeaderValue(exchange.getRequest(), headerKey)); - } - else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) { - String queryKey = parseQueryKey(labelKey); - if (StringUtils.isBlank(queryKey)) { - continue; - } - labels.put(labelKey, getQueryValue(exchange.getRequest(), queryKey)); - } - else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) { - String cookieKey = parseCookieKey(labelKey); - if (StringUtils.isBlank(cookieKey)) { - continue; - } - labels.put(labelKey, getCookieValue(exchange.getRequest(), cookieKey)); - } - else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) { - labels.put(labelKey, exchange.getRequest().getMethodValue()); - } - else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) { - labels.put(labelKey, exchange.getRequest().getURI().getPath()); - } - } - - return labels; - } - - public static Map resolve(HttpRequest request, Set labelKeys) { - if (CollectionUtils.isEmpty(labelKeys)) { - return Collections.emptyMap(); - } - - Map labels = new HashMap<>(); - - for (String labelKey : labelKeys) { - if (!isExpressionLabel(labelKey)) { - continue; - } - if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) { - String headerKey = parseHeaderKey(labelKey); - if (StringUtils.isBlank(headerKey)) { - continue; - } - labels.put(labelKey, getHeaderValue(request, headerKey)); - } - else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) { - String queryKey = parseQueryKey(labelKey); - if (StringUtils.isBlank(queryKey)) { - continue; - } - labels.put(labelKey, getQueryValue(request, queryKey)); - } - else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) { - labels.put(labelKey, request.getMethodValue()); - } - else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) { - labels.put(labelKey, request.getURI().getPath()); - } - } - - return labels; - } - - public static String parseHeaderKey(String expression) { - return expression.substring(LABEL_HEADER_PREFIX_LEN, expression.length() - 1); - } - - public static String parseQueryKey(String expression) { - return expression.substring(LABEL_QUERY_PREFIX_LEN, expression.length() - 1); - } - - public static String parseCookieKey(String expression) { - return expression.substring(LABEL_COOKIE_PREFIX_LEN, expression.length() - 1); - } - - public static String getQueryValue(String queryString, String queryKey) { - if (StringUtils.isBlank(queryString)) { - return StringUtils.EMPTY; - } - String[] queries = StringUtils.split(queryString, "&"); - if (queries == null || queries.length == 0) { - return StringUtils.EMPTY; - } - for (String query : queries) { - String[] queryKV = StringUtils.split(query, "="); - if (queryKV != null && queryKV.length == 2 && StringUtils.equals(queryKV[0], queryKey)) { - return queryKV[1]; - } - } - return StringUtils.EMPTY; - } - - public static String getCookieValue(Cookie[] cookies, String key) { - if (cookies == null || cookies.length == 0) { - return StringUtils.EMPTY; - } - for (Cookie cookie : cookies) { - if (StringUtils.equals(cookie.getName(), key)) { - return cookie.getValue(); - } - } - return StringUtils.EMPTY; - } - - public static String getHeaderValue(ServerHttpRequest request, String key) { - String value = request.getHeaders().getFirst(key); - if (value == null) { - return StringUtils.EMPTY; - } - return value; - } - - public static String getQueryValue(ServerHttpRequest request, String key) { - MultiValueMap queries = request.getQueryParams(); - if (CollectionUtils.isEmpty(queries)) { - return StringUtils.EMPTY; - } - String value = queries.getFirst(key); - if (value == null) { - return StringUtils.EMPTY; - } - return value; - } - - public static String getCookieValue(ServerHttpRequest request, String key) { - HttpCookie cookie = request.getCookies().getFirst(key); - if (cookie == null) { - return StringUtils.EMPTY; - } - return cookie.getValue(); - } - - public static String getHeaderValue(HttpRequest request, String key) { - HttpHeaders headers = request.getHeaders(); - return headers.getFirst(key); - } - - public static String getQueryValue(HttpRequest request, String key) { - String query = request.getURI().getQuery(); - return getQueryValue(query, key); - } - - public static String getFirstValue(Map> valueMaps, String key) { - if (CollectionUtils.isEmpty(valueMaps)) { - return StringUtils.EMPTY; - } - - Collection values = valueMaps.get(key); - - if (CollectionUtils.isEmpty(values)) { - return StringUtils.EMPTY; - } - - for (String value : values) { - return value; - } - - return StringUtils.EMPTY; - } -} 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 cb3f63801..234807853 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 @@ -78,6 +78,16 @@ public final class JacksonUtils { } } + public static T deserialize(String jsonStr, Class type) { + try { + return OM.readValue(jsonStr, type); + } + catch (JsonProcessingException e) { + LOG.error("Json to object failed. {}", type, e); + throw new RuntimeException("Json to object failed.", e); + } + } + /** * Json to Map. * @param jsonStr Json String diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.java new file mode 100644 index 000000000..2b85b9538 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.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.common.util.expresstion; + +import java.util.Collection; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; + +import org.springframework.util.CollectionUtils; + +/** + * the utils for parse label expression. + * + * @author lepdou 2022-05-13 + * @author cheese8 2022-06-20 + */ +public final class ExpressionLabelUtils { + + /** + * the expression prefix of header label. + */ + public static final String LABEL_HEADER_PREFIX = "${http.header."; + /** + * the length of expression header label prefix. + */ + public static final int LABEL_HEADER_PREFIX_LEN = LABEL_HEADER_PREFIX.length(); + /** + * the expression prefix of query. + */ + public static final String LABEL_QUERY_PREFIX = "${http.query."; + /** + * the length of expression query label prefix. + */ + public static final int LABEL_QUERY_PREFIX_LEN = LABEL_QUERY_PREFIX.length(); + /** + * the expression prefix of cookie. + */ + public static final String LABEL_COOKIE_PREFIX = "${http.cookie."; + /** + * the length of expression cookie label prefix. + */ + public static final int LABEL_COOKIE_PREFIX_LEN = LABEL_COOKIE_PREFIX.length(); + /** + * the expression of method. + */ + public static final String LABEL_METHOD = "${http.method}"; + /** + * the expression of uri. + */ + public static final String LABEL_URI = "${http.uri}"; + + /** + * the prefix of expression. + */ + public static final String LABEL_PREFIX = "${"; + + /** + * the suffix of expression. + */ + public static final String LABEL_SUFFIX = "}"; + + private ExpressionLabelUtils() { + } + + public static boolean isExpressionLabel(String labelKey) { + if (StringUtils.isEmpty(labelKey)) { + return false; + } + return StringUtils.startsWith(labelKey, LABEL_PREFIX) && StringUtils.endsWith(labelKey, LABEL_SUFFIX); + } + + public static String parseHeaderKey(String expression) { + return expression.substring(LABEL_HEADER_PREFIX_LEN, expression.length() - 1); + } + + public static String parseQueryKey(String expression) { + return expression.substring(LABEL_QUERY_PREFIX_LEN, expression.length() - 1); + } + + public static String parseCookieKey(String expression) { + return expression.substring(LABEL_COOKIE_PREFIX_LEN, expression.length() - 1); + } + + public static String getQueryValue(String queryString, String queryKey) { + if (StringUtils.isBlank(queryString)) { + return StringUtils.EMPTY; + } + String[] queries = StringUtils.split(queryString, "&"); + if (queries == null || queries.length == 0) { + return StringUtils.EMPTY; + } + for (String query : queries) { + String[] queryKV = StringUtils.split(query, "="); + if (queryKV != null && queryKV.length == 2 && StringUtils.equals(queryKV[0], queryKey)) { + return queryKV[1]; + } + } + return StringUtils.EMPTY; + } + + public static String getFirstValue(Map> valueMaps, String key) { + if (CollectionUtils.isEmpty(valueMaps)) { + return StringUtils.EMPTY; + } + + Collection values = valueMaps.get(key); + + if (CollectionUtils.isEmpty(values)) { + return StringUtils.EMPTY; + } + + for (String value : values) { + return value; + } + + return StringUtils.EMPTY; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ServletExpressionLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ServletExpressionLabelUtils.java new file mode 100644 index 000000000..cf504fffb --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ServletExpressionLabelUtils.java @@ -0,0 +1,96 @@ +/* + * 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.expresstion; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; + +import org.springframework.util.CollectionUtils; + +/** + * Parse labels from HttpServletRequest. + * @author lepdou 2022-07-11 + */ +public final class ServletExpressionLabelUtils { + + private ServletExpressionLabelUtils() { + } + + public static Map resolve(HttpServletRequest request, Set labelKeys) { + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + Map labels = new HashMap<>(); + + for (String labelKey : labelKeys) { + if (!ExpressionLabelUtils.isExpressionLabel(labelKey)) { + continue; + } + if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) { + String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey); + if (StringUtils.isBlank(headerKey)) { + continue; + } + labels.put(labelKey, request.getHeader(headerKey)); + } + else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) { + String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey); + if (StringUtils.isBlank(queryKey)) { + continue; + } + labels.put(labelKey, ExpressionLabelUtils.getQueryValue(request.getQueryString(), queryKey)); + } + else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_COOKIE_PREFIX)) { + String cookieKey = ExpressionLabelUtils.parseCookieKey(labelKey); + if (StringUtils.isBlank(cookieKey)) { + continue; + } + labels.put(labelKey, getCookieValue(request.getCookies(), cookieKey)); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) { + labels.put(labelKey, request.getMethod()); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) { + labels.put(labelKey, request.getRequestURI()); + } + } + + return labels; + } + + public static String getCookieValue(Cookie[] cookies, String key) { + if (cookies == null || cookies.length == 0) { + return StringUtils.EMPTY; + } + for (Cookie cookie : cookies) { + if (StringUtils.equals(cookie.getName(), key)) { + return cookie.getValue(); + } + } + return StringUtils.EMPTY; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/SpringWebExpressionLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/SpringWebExpressionLabelUtils.java new file mode 100644 index 000000000..ebf27607b --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/SpringWebExpressionLabelUtils.java @@ -0,0 +1,161 @@ +/* + * 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.expresstion; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; + +import org.springframework.http.HttpCookie; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.CollectionUtils; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; + +/** + * Parse labels from ServerWebExchange and HttpRequest. + * @author lepdou 2022-07-11 + */ +public final class SpringWebExpressionLabelUtils { + + private SpringWebExpressionLabelUtils() { + } + + public static Map resolve(ServerWebExchange exchange, Set labelKeys) { + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + Map labels = new HashMap<>(); + + for (String labelKey : labelKeys) { + if (!ExpressionLabelUtils.isExpressionLabel(labelKey)) { + continue; + } + if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) { + String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey); + if (StringUtils.isBlank(headerKey)) { + continue; + } + labels.put(labelKey, getHeaderValue(exchange.getRequest(), headerKey)); + } + else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) { + String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey); + if (StringUtils.isBlank(queryKey)) { + continue; + } + labels.put(labelKey, getQueryValue(exchange.getRequest(), queryKey)); + } + else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_COOKIE_PREFIX)) { + String cookieKey = ExpressionLabelUtils.parseCookieKey(labelKey); + if (StringUtils.isBlank(cookieKey)) { + continue; + } + labels.put(labelKey, getCookieValue(exchange.getRequest(), cookieKey)); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) { + labels.put(labelKey, exchange.getRequest().getMethodValue()); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) { + labels.put(labelKey, exchange.getRequest().getURI().getPath()); + } + } + + return labels; + } + + public static Map resolve(HttpRequest request, Set labelKeys) { + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + Map labels = new HashMap<>(); + + for (String labelKey : labelKeys) { + if (!ExpressionLabelUtils.isExpressionLabel(labelKey)) { + continue; + } + if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) { + String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey); + if (StringUtils.isBlank(headerKey)) { + continue; + } + labels.put(labelKey, getHeaderValue(request, headerKey)); + } + else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) { + String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey); + if (StringUtils.isBlank(queryKey)) { + continue; + } + labels.put(labelKey, getQueryValue(request, queryKey)); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) { + labels.put(labelKey, request.getMethodValue()); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) { + labels.put(labelKey, request.getURI().getPath()); + } + } + + return labels; + } + + public static String getHeaderValue(ServerHttpRequest request, String key) { + String value = request.getHeaders().getFirst(key); + if (value == null) { + return StringUtils.EMPTY; + } + return value; + } + + public static String getQueryValue(ServerHttpRequest request, String key) { + MultiValueMap queries = request.getQueryParams(); + if (CollectionUtils.isEmpty(queries)) { + return StringUtils.EMPTY; + } + String value = queries.getFirst(key); + if (value == null) { + return StringUtils.EMPTY; + } + return value; + } + + public static String getCookieValue(ServerHttpRequest request, String key) { + HttpCookie cookie = request.getCookies().getFirst(key); + if (cookie == null) { + return StringUtils.EMPTY; + } + return cookie.getValue(); + } + + public static String getHeaderValue(HttpRequest request, String key) { + HttpHeaders headers = request.getHeaders(); + return headers.getFirst(key); + } + + public static String getQueryValue(HttpRequest request, String key) { + String query = request.getURI().getQuery(); + return ExpressionLabelUtils.getQueryValue(query, key); + } +} 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 5dd740882..a8280d00a 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,7 +18,6 @@ package com.tencent.cloud.common.metadata.config; -import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter; import org.assertj.core.api.Assertions; import org.junit.Test; @@ -49,9 +48,6 @@ public class MetadataAutoConfigurationTest { .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) .run(context -> { Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); - Assertions.assertThat(context).hasSingleBean( - MetadataAutoConfiguration.MetadataScgFilterConfig.class); - Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class); }); } @@ -64,9 +60,6 @@ public class MetadataAutoConfigurationTest { .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) .run(context -> { Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); - Assertions.assertThat(context).hasSingleBean( - MetadataAutoConfiguration.MetadataScgFilterConfig.class); - Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class); }); } @@ -79,9 +72,6 @@ public class MetadataAutoConfigurationTest { .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) .run(context -> { Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); - Assertions.assertThat(context).hasSingleBean( - MetadataAutoConfiguration.MetadataScgFilterConfig.class); - Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class); }); } } diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/rule/OperationTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/rule/OperationTest.java new file mode 100644 index 000000000..33fe393ca --- /dev/null +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/rule/OperationTest.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.common.rule; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Test for {@link Operation}. + * @author lepdou 2022-07-12 + */ +@RunWith(MockitoJUnitRunner.class) +public class OperationTest { + + @Test + public void testEqual() { + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "v1", Operation.EQUALS.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "v2", Operation.EQUALS.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList(""), "v2", Operation.EQUALS.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "", Operation.EQUALS.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), null, Operation.EQUALS.getValue())); + Assert.assertFalse(Operation.match(Collections.emptyList(), "v1", Operation.EQUALS.getValue())); + } + + @Test + public void testNotEqual() { + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "v1", Operation.NOT_EQUALS.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "v2", Operation.NOT_EQUALS.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList(""), "v2", Operation.NOT_EQUALS.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "", Operation.NOT_EQUALS.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), null, Operation.NOT_EQUALS.getValue())); + Assert.assertTrue(Operation.match(Collections.emptyList(), "v1", Operation.NOT_EQUALS.getValue())); + } + + @Test + public void testIn() { + Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "v1", Operation.IN.getValue())); + Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "v2", Operation.IN.getValue())); + Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "v4", Operation.IN.getValue())); + Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "", Operation.IN.getValue())); + Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), null, Operation.IN.getValue())); + Assert.assertFalse(Operation.match(Collections.emptyList(), null, Operation.IN.getValue())); + } + + @Test + public void testNotIn() { + Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "v1", Operation.NOT_IN.getValue())); + Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "v2", Operation.NOT_IN.getValue())); + Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "v4", Operation.NOT_IN.getValue())); + Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "", Operation.NOT_IN.getValue())); + Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), null, Operation.NOT_IN.getValue())); + Assert.assertTrue(Operation.match(Collections.emptyList(), null, Operation.NOT_IN.getValue())); + } + + @Test + public void testEmpty() { + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), null, Operation.BLANK.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "", Operation.BLANK.getValue())); + Assert.assertTrue(Operation.match(Collections.emptyList(), null, Operation.BLANK.getValue())); + } + + @Test + public void testNotEmpty() { + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), null, Operation.NOT_BLANK.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "", Operation.NOT_BLANK.getValue())); + Assert.assertFalse(Operation.match(Collections.emptyList(), null, Operation.NOT_BLANK.getValue())); + Assert.assertTrue(Operation.match(Collections.emptyList(), "v1", Operation.NOT_BLANK.getValue())); + } + + @Test + public void testRegex() { + Assert.assertTrue(Operation.match(Collections.singletonList("v[1~10]"), "v1", Operation.REGEX.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v[1~10]"), "v12", Operation.REGEX.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v[1~10]*"), "v12", Operation.REGEX.getValue())); + } +} diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/ExpressionLabelUtilsTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/ExpressionLabelUtilsTest.java index e0410e5ec..0757d2da2 100644 --- a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/ExpressionLabelUtilsTest.java +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/ExpressionLabelUtilsTest.java @@ -23,6 +23,9 @@ import java.util.Map; import java.util.Set; import com.google.common.collect.Sets; +import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -105,7 +108,7 @@ public class ExpressionLabelUtilsTest { request.setMethod(HttpMethod.GET.name()); request.setRequestURI("/users"); - Map result = ExpressionLabelUtils.resolve(request, labelKeys); + Map result = ServletExpressionLabelUtils.resolve(request, labelKeys); Assert.assertEquals("zhangsan", result.get(validLabel1)); Assert.assertEquals("zhangsan", result.get(validLabel2)); @@ -149,7 +152,7 @@ public class ExpressionLabelUtilsTest { .cookie(new HttpCookie("uid", "zhangsan")).build(); MockServerWebExchange exchange = new MockServerWebExchange.Builder(httpRequest).build(); - Map result = ExpressionLabelUtils.resolve(exchange, labelKeys); + Map result = SpringWebExpressionLabelUtils.resolve(exchange, labelKeys); Assert.assertEquals("zhangsan", result.get(validLabel1)); Assert.assertEquals("zhangsan", result.get(validLabel2)); @@ -193,7 +196,7 @@ public class ExpressionLabelUtilsTest { request.setURI(URI.create("http://calleeService/user/get?uid=zhangsan")); request.getHeaders().add("uid", "zhangsan"); - Map result = ExpressionLabelUtils.resolve(request, labelKeys); + Map result = SpringWebExpressionLabelUtils.resolve(request, labelKeys); Assert.assertEquals("zhangsan", result.get(validLabel1)); Assert.assertEquals("zhangsan", result.get(validLabel2)); diff --git a/spring-cloud-tencent-coverage/pom.xml b/spring-cloud-tencent-coverage/pom.xml index 2fb6d5b66..d5704deea 100644 --- a/spring-cloud-tencent-coverage/pom.xml +++ b/spring-cloud-tencent-coverage/pom.xml @@ -69,6 +69,16 @@ spring-cloud-starter-tencent-polaris-config + + com.tencent.cloud + spring-cloud-tencent-featureenv-plugin + + + + com.tencent.cloud + spring-cloud-tencent-gateway-plugin + + com.tencent.cloud spring-cloud-tencent-pushgateway-plugin diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index ecc23b430..8f2430147 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -160,6 +160,19 @@ ${revision} + + + com.tencent.cloud + spring-cloud-tencent-featureenv-plugin + ${revision} + + + + com.tencent.cloud + spring-cloud-tencent-gateway-plugin + ${revision} + + com.tencent.cloud spring-cloud-tencent-pushgateway-plugin diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/pom.xml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/pom.xml index 1f681bb38..17c543546 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/pom.xml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/pom.xml @@ -19,6 +19,11 @@ spring-cloud-starter-tencent-polaris-discovery + + com.tencent.cloud + spring-cloud-starter-tencent-metadata-transfer + + org.springframework.boot spring-boot-starter-web @@ -29,4 +34,4 @@ esapi - \ No newline at end of file + diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/pom.xml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/pom.xml index 5d8f34fdf..c24f5e6cf 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/pom.xml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/pom.xml @@ -19,6 +19,11 @@ spring-cloud-starter-tencent-polaris-discovery + + com.tencent.cloud + spring-cloud-starter-tencent-metadata-transfer + + org.springframework.boot spring-boot-starter-web diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/pom.xml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/pom.xml index b6643b49d..e79635589 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/pom.xml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/pom.xml @@ -29,6 +29,16 @@ spring-cloud-starter-tencent-polaris-router + + com.tencent.cloud + spring-cloud-tencent-gateway-plugin + + + + com.tencent.cloud + spring-cloud-tencent-featureenv-plugin + + org.springframework.cloud spring-cloud-starter-gateway diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml index 55f03dfa3..81a88dd67 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml @@ -6,9 +6,15 @@ spring: name: GatewayScgService cloud: tencent: - metadata: - content: - a: 1 + plugin: + scg: + staining: + enabled: true + rule-staining: + enabled: true + router: + feature-env: + enabled: true polaris: address: grpc://183.47.111.80:8091 namespace: default diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/README-zh.md b/spring-cloud-tencent-examples/polaris-router-featureenv-example/README-zh.md new file mode 100644 index 000000000..85a17f4f0 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/README-zh.md @@ -0,0 +1,174 @@ +# 多测试环境样例说明 + +[English](./README.md) | 简体中文 + +## 一、部署结构 + + + +如上图所示,一共有三个环境: + +1. 基线环境,包含 FrontService、MiddleService、BackendService +2. feature1 环境,包含 MiddleService、BackendService +3. feature2 环境,包含 FrontService、BackendService + +并且在入口处,部署网关服务。 + +三条请求链路: + +1. 基线环境链路,Gateway -> FrontService(基线) -> MiddleService(基线) -> BackendService(基线) +2. feature1 环境链路,Gateway -> FrontService(基线) -> MiddleService(feature1) -> BackendService(feature1) +3. feature2 环境链路,Gateway -> FrontService(feature2) -> MiddleService(基线) -> BackendService(feature2) + +## 二、运行样例 + +无需任何代码变更,直接启动 base、feature1、feature2、featureenv-gateway 下所有应用即可。 + +应用默认指向北极星官方的体验环境,启动成功后可直接到体验站点查看服务注册数据。 + +- 管控台地址: http://14.116.241.63:8080/ + - 账号:polaris + - 密码:polaris + +## 三、测试 + +### 方式一:客户端打标 + +#### 基线环境链路 + +```` +curl http://127.0.0.1:9999/featureenv-front-example/router/rest +```` + +响应结果(base 表示基线环境) + +```` +featureenv-front-example[base] -> featureenv-middle-example[base] -> featureenv-backend-example[base] +```` + +#### feature1 环境链路 + +通过 X-Polaris-Metadata-Transitive-featureenv 请求头指定特性环境。 + +```` +curl -H'X-Polaris-Metadata-Transitive-featureenv:feature1' http://127.0.0.1:9999/featureenv-front-example/router/rest +```` + +响应结果 + +```` +featureenv-front-example[base] -> featureenv-middle-example[feature1] -> featureenv-backend-example[feature1] +```` + +#### feature2 环境链路 + +通过 X-Polaris-Metadata-Transitive-featureenv 请求头指定特性环境。 + +```` +curl -H'X-Polaris-Metadata-Transitive-featureenv:feature2' http://127.0.0.1:9999/featureenv-front-example/router/rest +```` + +响应结果 + +```` +featureenv-front-example[feature2] -> featureenv-middle-example[base] -> featureenv-backend-example[feature2] +```` + +### 方式二:网关流量染色 + +模拟一种实际的场景,假设客户端请求有一个 uid 请求参数,期望: + +1. uid=1000 的请求打到 feature1 环境 +2. uid=2000 的请求打到 feature2 环境 +3. 其它 uid 的请求打到基线环境 + +**配置染色规则** + +配置地址:http://14.116.241.63:8080/#/filegroup-detail?group=featureenv-gateway&namespace=default + +修改 rule/staining.json 配置文件,填写以下规则: + +````json +{ + "rules": [ + { + "conditions": [ + { + "key": "${http.query.uid}", + "values": [ + "1000" + ], + "operation": "EQUALS" + } + ], + "labels": [ + { + "key": "featureenv", + "value": "feature1" + } + ] + }, + { + "conditions": [ + { + "key": "${http.query.uid}", + "values": [ + "2000" + ], + "operation": "EQUALS" + } + ], + "labels": [ + { + "key": "featureenv", + "value": "feature2" + } + ] + } + ] +} +```` + +填写完后发布配置即可。 + +#### 基线环境链路 + +```` +curl http://127.0.0.1:9999/featureenv-front-example/router/rest?uid=3000 +```` + +响应结果(base 表示基线环境) + +```` +featureenv-front-example[base] -> featureenv-middle-example[base] -> featureenv-backend-example[base] +```` + +#### feature1 环境链路 + +通过 X-Polaris-Metadata-Transitive-featureenv 请求头指定特性环境。 + +```` +curl http://127.0.0.1:9999/featureenv-front-example/router/rest?uid=1000 +```` + +响应结果 + +```` +featureenv-front-example[base] -> featureenv-middle-example[feature1] -> featureenv-backend-example[feature1] +```` + +#### feature2 环境链路 + +通过 X-Polaris-Metadata-Transitive-featureenv 请求头指定特性环境。 + +```` +curl http://127.0.0.1:9999/featureenv-front-example/router/rest?uid=2000 +```` + +响应结果 + +```` +featureenv-front-example[feature2] -> featureenv-middle-example[base] -> featureenv-backend-example[feature2] +```` + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/README.md b/spring-cloud-tencent-examples/polaris-router-featureenv-example/README.md new file mode 100644 index 000000000..e0853aef2 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/README.md @@ -0,0 +1,179 @@ +## A Multi-Feature Environment Example + +English | [简体中文](./README-zh.md) + +## I. Deployment Structure + +multi-feature environment structure + +As shown in the figure above, there are three environments. + +1. `baseline` environment, including `FrontService`, `MiddleService`, `BackendService` +2. `feature1` environment, including `MiddleService`, `BackendService` +3. `feature2` environment, including `FrontService`, `BackendService` + +And at the entrance, deploy the `gateway` service. + +Three request links. + +1. `baseline` environment link, `Gateway` -> `FrontService`(baseline) -> `MiddleService`(baseline) -> `BackendService`( + baseline) +2. `feature1` environment link, `Gateway` -> `FrontService`(baseline) -> `MiddleService`(feature1) -> `BackendService`( + feature1) +3. `feature2` environment link, `Gateway` -> `FrontService`(feature2) -> `MiddleService`(baseline) -> `BackendService`( + feature2) + +## II. Running + +Without any code changes, just start all the applications under `base`, `feature1`, `feature2`, `featureenv-gateway` +directly. + +By default, the applications point to the official Polaris experience environment, and you can directly view the service +registration data at the experience site after a successful launch. + +- Console address: http://14.116.241.63:8080/ + - Account:polaris + - Password: polaris + +## III. Testing + +### Mode 1: Client Request With `featureenv` Label + +#### `baseline` environment link + +```` +curl http://127.0.0.1:9999/featureenv-front-example/router/rest +```` + +Response results (base indicates baseline environment) + +```` +featureenv-front-example[base] -> featureenv-middle-example[base] -> featureenv-backend-example[base] +```` + +#### `feature1` environment link + +Specify the feature environment via the `X-Polaris-Metadata-Transitive-featureenv` request header. + +```` +curl -H'X-Polaris-Metadata-Transitive-featureenv:feature1' http://127.0.0.1:9999/featureenv-front-example/router/rest +```` + +Response results + +```` +featureenv-front-example[base] -> featureenv-middle-example[feature1] -> featureenv-backend-example[feature1] +```` + +#### `feature2` environment link + +Specify the feature environment via the `X-Polaris-Metadata-Transitive-featureenv` request header. + +```` +curl -H'X-Polaris-Metadata-Transitive-featureenv:feature2' http://127.0.0.1:9999/featureenv-front-example/router/rest +```` + +Response results + +```` +featureenv-front-example[feature2] -> featureenv-middle-example[base] -> featureenv-backend-example[feature2] +```` + +### Mode 2: Gateway traffic staining + +Simulate a real-world scenario, assuming that the client request has a uid request parameter and expects: + +1. `uid=1000` requests hit the `feature1` environment +2. `uid=2000` requests hit the `feature2` environment +3. requests with other uid hit the `baseline` environment + +**Configure coloring rules** + +Polaris Configuration Address:http://14.116.241.63:8080/#/filegroup-detail?group=featureenv-gateway&namespace=default + +Modify the `rule/staining.json` configuration file and fill in the following rule: + +````json +{ + "rules": [ + { + "conditions": [ + { + "key": "${http.query.uid}", + "values": [ + "1000" + ], + "operation": "EQUALS" + } + ], + "labels": [ + { + "key": "featureenv", + "value": "feature1" + } + ] + }, + { + "conditions": [ + { + "key": "${http.query.uid}", + "values": [ + "2000" + ], + "operation": "EQUALS" + } + ], + "labels": [ + { + "key": "featureenv", + "value": "feature2" + } + ] + } + ] +} +```` + +Just fill out and publish the configuration. + +#### `baseline` Environment Link + +```` +curl http://127.0.0.1:9999/featureenv-front-example/router/rest?uid=3000 +```` + +Response results (base indicates baseline environment) + +```` +featureenv-front-example[base] -> featureenv-middle-example[base] -> featureenv-backend-example[base] +```` + +#### `feature1` Environment Link + +Specify the feature environment via the `X-Polaris-Metadata-Transitive-featureenv` request header. + +```` +curl http://127.0.0.1:9999/featureenv-front-example/router/rest?uid=1000 +```` + +Response results + +```` +featureenv-front-example[base] -> featureenv-middle-example[feature1] -> featureenv-backend-example[feature1] +```` + +#### `feature2` Environment Link + +Specify the feature environment via the `X-Polaris-Metadata-Transitive-featureenv` request header. + +```` +curl http://127.0.0.1:9999/featureenv-front-example/router/rest?uid=2000 +```` + +Response results + +```` +featureenv-front-example[feature2] -> featureenv-middle-example[base] -> featureenv-backend-example[feature2] +```` + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/pom.xml new file mode 100644 index 000000000..4b1c3d1e4 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/pom.xml @@ -0,0 +1,16 @@ + + + + base + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + base-backend + + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/basebackend/BackendController.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/basebackend/BackendController.java new file mode 100644 index 000000000..db7121ded --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/basebackend/BackendController.java @@ -0,0 +1,44 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.router.featureenv.basebackend; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author lepdou 2022-07-20 + */ +@RestController +@RequestMapping("/router") +public class BackendController { + + @Value("${spring.application.name}") + private String appName; + + /** + * Get information of callee. + * @return information of callee + */ + @GetMapping("/rest") + public String rest() { + return appName + "[base]"; + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/basebackend/BaseBackendApplication.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/basebackend/BaseBackendApplication.java new file mode 100644 index 000000000..755704d1d --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/basebackend/BaseBackendApplication.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.polaris.router.featureenv.basebackend; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * @author lepdou 2022-07-20 + */ +@SpringBootApplication +@EnableFeignClients +public class BaseBackendApplication { + + public static void main(String[] args) { + SpringApplication.run(BaseBackendApplication.class, args); + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..34267f03e --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-backend/src/main/resources/bootstrap.yml @@ -0,0 +1,15 @@ +server: + session-timeout: 1800 + port: 10002 +spring: + application: + name: featureenv-backend-example + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + enabled: true +logging: + level: + org.springframework.cloud.gateway: info + com.tencent.cloud.polaris: debug diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/pom.xml new file mode 100644 index 000000000..38e365cf5 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/pom.xml @@ -0,0 +1,16 @@ + + + + base + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + base-front + + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/basefront/BaseFrontApplication.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/basefront/BaseFrontApplication.java new file mode 100644 index 000000000..4197f2367 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/basefront/BaseFrontApplication.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.polaris.router.featureenv.basefront; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * @author lepdou 2022-07-20 + */ +@SpringBootApplication +@EnableFeignClients +public class BaseFrontApplication { + + public static void main(String[] args) { + SpringApplication.run(BaseFrontApplication.class, args); + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/basefront/FrontController.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/basefront/FrontController.java new file mode 100644 index 000000000..08bc61d5e --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/basefront/FrontController.java @@ -0,0 +1,51 @@ +/* + * 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.router.featureenv.basefront; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author lepdou 2022-07-20 + */ +@RestController +@RequestMapping("/router") +public class FrontController { + + @Value("${spring.application.name}") + private String appName; + + @Autowired + private MiddleService middleService; + + /** + * Get information of callee. + * @return information of callee + */ + @GetMapping("/rest") + public String rest() { + String curName = appName + "[base]"; + String resp = middleService.rest(); + + return curName + " -> " + resp; + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/basefront/MiddleService.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/basefront/MiddleService.java new file mode 100644 index 000000000..0602481f7 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/basefront/MiddleService.java @@ -0,0 +1,33 @@ +/* + * 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.router.featureenv.basefront; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * @author lepdou 2022-07-20 + */ +@FeignClient("featureenv-middle-example") +public interface MiddleService { + + @GetMapping("/router/rest") + String rest(); + +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..aab4e3e1d --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-front/src/main/resources/bootstrap.yml @@ -0,0 +1,15 @@ +server: + session-timeout: 1800 + port: 10000 +spring: + application: + name: featureenv-front-example + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + enabled: true +logging: + level: + org.springframework.cloud.gateway: info + com.tencent.cloud.polaris: debug diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/pom.xml new file mode 100644 index 000000000..8b0ed0024 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/pom.xml @@ -0,0 +1,18 @@ + + + + base + com.tencent.cloud + ${revision} + ../pom.xml + + + 4.0.0 + + base-middle + + + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/basemiddle/BackendService.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/basemiddle/BackendService.java new file mode 100644 index 000000000..35818bb39 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/basemiddle/BackendService.java @@ -0,0 +1,33 @@ +/* + * 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.router.featureenv.basemiddle; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * @author lepdou 2022-07-20 + */ +@FeignClient("featureenv-backend-example") +public interface BackendService { + + @GetMapping("/router/rest") + String rest(); + +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/basemiddle/BaseMiddleApplication.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/basemiddle/BaseMiddleApplication.java new file mode 100644 index 000000000..2a88780cf --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/basemiddle/BaseMiddleApplication.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.polaris.router.featureenv.basemiddle; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * @author lepdou 2022-07-20 + */ +@SpringBootApplication +@EnableFeignClients +public class BaseMiddleApplication { + + public static void main(String[] args) { + SpringApplication.run(BaseMiddleApplication.class, args); + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/basemiddle/MiddleController.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/basemiddle/MiddleController.java new file mode 100644 index 000000000..276993caf --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/basemiddle/MiddleController.java @@ -0,0 +1,51 @@ +/* + * 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.router.featureenv.basemiddle; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author lepdou 2022-07-20 + */ +@RestController +@RequestMapping("/router") +public class MiddleController { + + @Value("${spring.application.name}") + private String appName; + + @Autowired + private BackendService backendService; + + /** + * Get information of callee. + * @return information of callee + */ + @GetMapping("/rest") + public String rest() { + String curName = appName + "[base]"; + String resp = backendService.rest(); + + return curName + " -> " + resp; + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..d4a9b9312 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/base-middle/src/main/resources/bootstrap.yml @@ -0,0 +1,15 @@ +server: + session-timeout: 1800 + port: 10001 +spring: + application: + name: featureenv-middle-example + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + enabled: true +logging: + level: + org.springframework.cloud.gateway: info + com.tencent.cloud.polaris: debug diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/pom.xml new file mode 100644 index 000000000..0dc0c3d3d --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/base/pom.xml @@ -0,0 +1,33 @@ + + + + polaris-router-featureenv-example + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + base + pom + + + base-front + base-middle + base-backend + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/pom.xml new file mode 100644 index 000000000..57fa48b17 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/pom.xml @@ -0,0 +1,16 @@ + + + + feature1 + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + feature1-backend + + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1backend/BackendController.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1backend/BackendController.java new file mode 100644 index 000000000..9291ba52f --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1backend/BackendController.java @@ -0,0 +1,51 @@ +/* + * 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.router.featureenv.feature1backend; + +import com.tencent.cloud.common.metadata.StaticMetadataManager; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author lepdou 2022-07-20 + */ +@RestController +@RequestMapping("/router") +public class BackendController { + + @Autowired + private StaticMetadataManager staticMetadataManager; + + @Value("${spring.application.name}") + private String appName; + + /** + * Get information of callee. + * @return information of callee + */ + @GetMapping("/rest") + public String rest() { + String featureEnv = staticMetadataManager.getMergedStaticMetadata().get("featureenv"); + return appName + "[" + featureEnv + "]"; + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1backend/Feature1BackendApplication.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1backend/Feature1BackendApplication.java new file mode 100644 index 000000000..4af96c17f --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1backend/Feature1BackendApplication.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.polaris.router.featureenv.feature1backend; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * @author lepdou 2022-07-20 + */ +@SpringBootApplication +@EnableFeignClients +public class Feature1BackendApplication { + + public static void main(String[] args) { + SpringApplication.run(Feature1BackendApplication.class, args); + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..04b3ca3a8 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-backend/src/main/resources/bootstrap.yml @@ -0,0 +1,19 @@ +server: + session-timeout: 1800 + port: 11002 +spring: + application: + name: featureenv-backend-example + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + enabled: true + tencent: + metadata: + content: + featureenv: feature1 +logging: + level: + org.springframework.cloud.gateway: info + com.tencent.cloud.polaris: debug diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/pom.xml new file mode 100644 index 000000000..873bd1394 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/pom.xml @@ -0,0 +1,16 @@ + + + + feature1 + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + feature1-middle + + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1middle/BackendService.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1middle/BackendService.java new file mode 100644 index 000000000..65b91a922 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1middle/BackendService.java @@ -0,0 +1,33 @@ +/* + * 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.router.featureenv.feature1middle; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * @author lepdou 2022-07-20 + */ +@FeignClient("featureenv-backend-example") +public interface BackendService { + + @GetMapping("/router/rest") + String rest(); + +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1middle/Feature1MiddleApplication.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1middle/Feature1MiddleApplication.java new file mode 100644 index 000000000..c46db04ab --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1middle/Feature1MiddleApplication.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.polaris.router.featureenv.feature1middle; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * @author lepdou 2022-07-20 + */ +@SpringBootApplication +@EnableFeignClients +public class Feature1MiddleApplication { + + public static void main(String[] args) { + SpringApplication.run(Feature1MiddleApplication.class, args); + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1middle/MiddleController.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1middle/MiddleController.java new file mode 100644 index 000000000..41da83613 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature1middle/MiddleController.java @@ -0,0 +1,58 @@ +/* + * 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.router.featureenv.feature1middle; + +import com.tencent.cloud.common.metadata.StaticMetadataManager; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author lepdou 2022-07-20 + */ +@RestController +@RequestMapping("/router") +public class MiddleController { + + @Value("${spring.application.name}") + private String appName; + + @Autowired + private BackendService backendService; + + @Autowired + private StaticMetadataManager staticMetadataManager; + + /** + * Get information of callee. + * @return information of callee + */ + @GetMapping("/rest") + public String rest() { + String featureEnv = staticMetadataManager.getMergedStaticMetadata().get("featureenv"); + + String curName = appName + "[" + featureEnv + "]"; + String resp = backendService.rest(); + + return curName + " -> " + resp; + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..1a46790a3 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/feature1-middle/src/main/resources/bootstrap.yml @@ -0,0 +1,19 @@ +server: + session-timeout: 1800 + port: 11001 +spring: + application: + name: featureenv-middle-example + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + enabled: true + tencent: + metadata: + content: + featureenv: feature1 +logging: + level: + org.springframework.cloud.gateway: info + com.tencent.cloud.polaris: debug diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/pom.xml new file mode 100644 index 000000000..b364b109c --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature1/pom.xml @@ -0,0 +1,32 @@ + + + + polaris-router-featureenv-example + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + feature1 + pom + + + feature1-middle + feature1-backend + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/pom.xml new file mode 100644 index 000000000..bc2f20a6f --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/pom.xml @@ -0,0 +1,16 @@ + + + + feature2 + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + feature2-backend + + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2backend/BackendController.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2backend/BackendController.java new file mode 100644 index 000000000..8b416fb8c --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2backend/BackendController.java @@ -0,0 +1,51 @@ +/* + * 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.router.featureenv.feature2backend; + +import com.tencent.cloud.common.metadata.StaticMetadataManager; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author lepdou 2022-07-20 + */ +@RestController +@RequestMapping("/router") +public class BackendController { + + @Autowired + private StaticMetadataManager staticMetadataManager; + + @Value("${spring.application.name}") + private String appName; + + /** + * Get information of callee. + * @return information of callee + */ + @GetMapping("/rest") + public String rest() { + String featureEnv = staticMetadataManager.getMergedStaticMetadata().get("featureenv"); + return appName + "[" + featureEnv + "]"; + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2backend/Feature2BackendApplication.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2backend/Feature2BackendApplication.java new file mode 100644 index 000000000..fe1bd3c60 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2backend/Feature2BackendApplication.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.polaris.router.featureenv.feature2backend; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * @author lepdou 2022-07-20 + */ +@SpringBootApplication +@EnableFeignClients +public class Feature2BackendApplication { + + public static void main(String[] args) { + SpringApplication.run(Feature2BackendApplication.class, args); + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..aece1f3f8 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-backend/src/main/resources/bootstrap.yml @@ -0,0 +1,19 @@ +server: + session-timeout: 1800 + port: 12002 +spring: + application: + name: featureenv-backend-example + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + enabled: true + tencent: + metadata: + content: + featureenv: feature2 +logging: + level: + org.springframework.cloud.gateway: info + com.tencent.cloud.polaris: debug diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/pom.xml new file mode 100644 index 000000000..fb4268738 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/pom.xml @@ -0,0 +1,17 @@ + + + + feature2 + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + feature2-front + + + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2front/Feature2FrontApplication.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2front/Feature2FrontApplication.java new file mode 100644 index 000000000..a6b8bd1fa --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2front/Feature2FrontApplication.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.polaris.router.featureenv.feature2front; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * @author lepdou 2022-07-20 + */ +@SpringBootApplication +@EnableFeignClients +public class Feature2FrontApplication { + + public static void main(String[] args) { + SpringApplication.run(Feature2FrontApplication.class, args); + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2front/FrontController.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2front/FrontController.java new file mode 100644 index 000000000..0c019d742 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2front/FrontController.java @@ -0,0 +1,58 @@ +/* + * 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.router.featureenv.feature2front; + +import com.tencent.cloud.common.metadata.StaticMetadataManager; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author lepdou 2022-07-20 + */ +@RestController +@RequestMapping("/router") +public class FrontController { + + @Autowired + private StaticMetadataManager staticMetadataManager; + + @Value("${spring.application.name}") + private String appName; + + @Autowired + private MiddleService middleService; + + /** + * Get information of callee. + * @return information of callee + */ + @GetMapping("/rest") + public String rest() { + String featureEnv = staticMetadataManager.getMergedStaticMetadata().get("featureenv"); + + String curName = appName + "[" + featureEnv + "]"; + String resp = middleService.rest(); + + return curName + " -> " + resp; + } +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2front/MiddleService.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2front/MiddleService.java new file mode 100644 index 000000000..2e0690fdd --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/java/com/tencent/cloud/polaris/router/featureenv/feature2front/MiddleService.java @@ -0,0 +1,33 @@ +/* + * 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.router.featureenv.feature2front; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * @author lepdou 2022-07-20 + */ +@FeignClient("featureenv-middle-example") +public interface MiddleService { + + @GetMapping("/router/rest") + String rest(); + +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..9fdaa2add --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/feature2-front/src/main/resources/bootstrap.yml @@ -0,0 +1,19 @@ +server: + session-timeout: 1800 + port: 12000 +spring: + application: + name: featureenv-front-example + cloud: + polaris: + address: grpc://183.47.111.80:8091 + namespace: default + enabled: true + tencent: + metadata: + content: + featureenv: feature2 +logging: + level: + org.springframework.cloud.gateway: info + com.tencent.cloud.polaris: debug diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/pom.xml new file mode 100644 index 000000000..f14cbd7d6 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/feature2/pom.xml @@ -0,0 +1,32 @@ + + + + polaris-router-featureenv-example + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + feature2 + pom + + + feature2-front + feature2-backend + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/pom.xml new file mode 100644 index 000000000..f26708819 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/pom.xml @@ -0,0 +1,30 @@ + + + + polaris-router-featureenv-example + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + featureenv-gateway + + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + com.tencent.cloud + spring-cloud-tencent-gateway-plugin + + + com.tencent.cloud + spring-cloud-tencent-featureenv-plugin + + + diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/src/main/java/com/tencent/cloud/polaris/featureenv/gateway/FeatureEnvScgApplication.java b/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/src/main/java/com/tencent/cloud/polaris/featureenv/gateway/FeatureEnvScgApplication.java new file mode 100644 index 000000000..41835822d --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/src/main/java/com/tencent/cloud/polaris/featureenv/gateway/FeatureEnvScgApplication.java @@ -0,0 +1,33 @@ +/* + * 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.featureenv.gateway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author lepdou 2022-07-20 + */ +@SpringBootApplication +public class FeatureEnvScgApplication { + + public static void main(String[] args) { + SpringApplication.run(FeatureEnvScgApplication.class, args); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..6e1972557 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/featureenv-gateway/src/main/resources/bootstrap.yml @@ -0,0 +1,68 @@ +server: + session-timeout: 1800 + port: 9999 +spring: + application: + name: featureenv-gateway + 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 + ''/(?.*)''' + replacement: '''/$\{remaining}''' + 'filters[1]': + name: Retry + args: + retries: 3 + exceptions: + '[0]': '''java.net.ConnectException''' + '[1]': '''java.io.IOException''' + statuses: + '[0]': '''BAD_GATEWAY''' + '[1]': '''SERVICE_UNAVAILABLE''' + series: + '[0]': '''CLIENT_ERROR''' + methods: + '[0]': '''GET''' + '[1]': '''POST''' + '[2]': '''PUT''' + '[3]': '''DELETE''' + backoff: + firstBackoff: '''100ms''' + maxBackoff: '''500ms''' + factor: 2 + basedOnPreviousValue: false + routes: + - id: featureenv-front-example + uri: lb://featureenv-front-example + predicates: + - Path=/featureenv-front-example/** + filters: + - StripPrefix=1 + +logging: + level: + org.springframework.cloud.gateway: info + com.tencent.cloud.polaris: debug diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/imgs/structs.png b/spring-cloud-tencent-examples/polaris-router-featureenv-example/imgs/structs.png new file mode 100644 index 0000000000000000000000000000000000000000..2b8a9c8a11853359c386603b7aeaae595f81163d GIT binary patch literal 56389 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 literal 0 HcmV?d00001 diff --git a/spring-cloud-tencent-examples/polaris-router-featureenv-example/pom.xml b/spring-cloud-tencent-examples/polaris-router-featureenv-example/pom.xml new file mode 100644 index 000000000..c9308f4ba --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-router-featureenv-example/pom.xml @@ -0,0 +1,34 @@ + + + + spring-cloud-tencent-examples + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + polaris-router-featureenv-example + pom + + + base + feature1 + feature2 + featureenv-gateway + + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-discovery + + + + com.tencent.cloud + spring-cloud-tencent-featureenv-plugin + + + diff --git a/spring-cloud-tencent-examples/pom.xml b/spring-cloud-tencent-examples/pom.xml index 40f7b2ed6..ada8e13e2 100644 --- a/spring-cloud-tencent-examples/pom.xml +++ b/spring-cloud-tencent-examples/pom.xml @@ -1,41 +1,42 @@ - - spring-cloud-tencent - com.tencent.cloud - ${revision} - ../pom.xml - - 4.0.0 + 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"> + + spring-cloud-tencent + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 - spring-cloud-tencent-examples - pom - Spring Cloud Tencent Examples - Examples of Spring Cloud Tencent + spring-cloud-tencent-examples + pom + Spring Cloud Tencent Examples + Examples of Spring Cloud Tencent - - polaris-discovery-example - polaris-ratelimit-example - polaris-circuitbreaker-example - polaris-gateway-example + + polaris-discovery-example + polaris-ratelimit-example + polaris-circuitbreaker-example + polaris-gateway-example polaris-config-example polaris-router-example metadata-transfer-example polaris-router-grayrelease-example + polaris-router-featureenv-example - - true - + + true + org.owasp.esapi esapi - 2.1.0.1 + 2.5.0.0 diff --git a/spring-cloud-tencent-plugin-starters/pom.xml b/spring-cloud-tencent-plugin-starters/pom.xml index 1d139ae7a..cba7562ec 100644 --- a/spring-cloud-tencent-plugin-starters/pom.xml +++ b/spring-cloud-tencent-plugin-starters/pom.xml @@ -15,6 +15,8 @@ Spring Cloud Starter Tencent Solution + spring-cloud-tencent-featureenv-plugin + spring-cloud-tencent-gateway-plugin spring-cloud-tencent-pushgateway-plugin diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/pom.xml b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/pom.xml new file mode 100644 index 000000000..25cce93dd --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/pom.xml @@ -0,0 +1,30 @@ + + + + spring-cloud-tencent-plugin-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-featureenv-plugin + Spring Cloud Tencent Feature Environment Plugin + + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-router + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvAutoConfiguration.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvAutoConfiguration.java new file mode 100644 index 000000000..d341ba345 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvAutoConfiguration.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.plugin.featureenv; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Auto configuration for feature env. + * @author lepdou 2022-07-06 + */ +@Configuration +@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.router.feature-env.enabled", matchIfMissing = true) +public class FeatureEnvAutoConfiguration { + + @Bean + public FeatureEnvProperties featureEnvProperties() { + return new FeatureEnvProperties(); + } + + @Bean + public FeatureEnvRouterRequestInterceptor featureEnvRouterRequestInterceptor() { + return new FeatureEnvRouterRequestInterceptor(); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvProperties.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvProperties.java new file mode 100644 index 000000000..4aa0aa451 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvProperties.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.plugin.featureenv; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * The properties for feature env. + * @author lepdou 2022-07-12 + */ +@ConfigurationProperties("spring.cloud.tencent.plugin.router.feature-env") +public class FeatureEnvProperties { + + private boolean enabled; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptor.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptor.java new file mode 100644 index 000000000..05bbdaf40 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptor.java @@ -0,0 +1,65 @@ +/* + * 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.plugin.featureenv; + +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.polaris.api.rpc.MetadataFailoverType; +import com.tencent.polaris.plugins.router.metadata.MetadataRouter; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; +import org.apache.commons.lang.StringUtils; + +/** + * Build metadata router context for feature env scene. + * @author lepdou 2022-07-06 + */ +public class FeatureEnvRouterRequestInterceptor implements RouterRequestInterceptor { + + private static final String LABEL_KEY_FEATURE_ENV_ROUTER_KEY = "system-feature-env-router-label"; + private static final String DEFAULT_FEATURE_ENV_ROUTER_LABEL = "featureenv"; + private static final String NOT_EXISTED_ENV = "NOT_EXISTED_ENV"; + + @Override + public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) { + //1. get feature env router label key + String envLabelKey = routerContext.getLabel(LABEL_KEY_FEATURE_ENV_ROUTER_KEY); + if (StringUtils.isBlank(envLabelKey)) { + envLabelKey = DEFAULT_FEATURE_ENV_ROUTER_LABEL; + } + + //2. get feature env router label value + String envLabelValue = routerContext.getLabel(envLabelKey); + if (envLabelValue == null) { + // router to base env when not matched feature env + envLabelValue = NOT_EXISTED_ENV; + } + + //3. set env metadata to router request + Map envMetadata = new HashMap<>(); + envMetadata.put(envLabelKey, envLabelValue); + + request.addRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, envMetadata); + + //4. set failover type to others + request.setMetadataFailoverType(MetadataFailoverType.METADATAFAILOVERNOTKEY); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 000000000..e67ca364e --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,10 @@ +{ + "properties": [ + { + "name": "spring.cloud.tencent.plugin.router.feature-env.enabled", + "type": "java.lang.Boolean", + "defaultValue": true, + "description": "the switch for feature env plugin." + } + ] +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..b61fbdfab --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.plugin.featureenv.FeatureEnvAutoConfiguration diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/test/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptorTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/test/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptorTest.java new file mode 100644 index 000000000..8a5a6f35a --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/test/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptorTest.java @@ -0,0 +1,104 @@ +/* + * 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.plugin.featureenv; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.polaris.api.pojo.DefaultServiceInstances; +import com.tencent.polaris.api.pojo.ServiceInstances; +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.plugins.router.metadata.MetadataRouter; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Test for {@link FeatureEnvRouterRequestInterceptor}. + * @author lepdou 2022-07-12 + */ +@RunWith(MockitoJUnitRunner.class) +public class FeatureEnvRouterRequestInterceptorTest { + + @Test + public void testDefaultRouterKey() { + Map labels = new HashMap<>(); + labels.put("featureenv", "blue"); + PolarisRouterContext routerContext = new PolarisRouterContext(); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + + ProcessRoutersRequest request = new ProcessRoutersRequest(); + ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>()); + request.setDstInstances(serviceInstances); + + FeatureEnvRouterRequestInterceptor interceptor = new FeatureEnvRouterRequestInterceptor(); + + interceptor.apply(request, routerContext); + + Map metadataRouterLabels = request.getRouterMetadata().get(MetadataRouter.ROUTER_TYPE_METADATA); + Assert.assertEquals(1, metadataRouterLabels.size()); + Assert.assertEquals("blue", metadataRouterLabels.get("featureenv")); + } + + @Test + public void testSpecifyRouterKey() { + Map labels = new HashMap<>(); + labels.put("system-feature-env-router-label", "specify-env"); + labels.put("specify-env", "blue"); + PolarisRouterContext routerContext = new PolarisRouterContext(); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + + ProcessRoutersRequest request = new ProcessRoutersRequest(); + ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>()); + request.setDstInstances(serviceInstances); + + FeatureEnvRouterRequestInterceptor interceptor = new FeatureEnvRouterRequestInterceptor(); + + interceptor.apply(request, routerContext); + + Map metadataRouterLabels = request.getRouterMetadata().get(MetadataRouter.ROUTER_TYPE_METADATA); + Assert.assertEquals(1, metadataRouterLabels.size()); + Assert.assertEquals("blue", metadataRouterLabels.get("specify-env")); + } + + @Test + public void testNotExistedEnvLabel() { + Map labels = new HashMap<>(); + labels.put("system-feature-env-router-label", "specify-env"); + PolarisRouterContext routerContext = new PolarisRouterContext(); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + + ProcessRoutersRequest request = new ProcessRoutersRequest(); + ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>()); + request.setDstInstances(serviceInstances); + + FeatureEnvRouterRequestInterceptor interceptor = new FeatureEnvRouterRequestInterceptor(); + + interceptor.apply(request, routerContext); + + Map metadataRouterLabels = request.getRouterMetadata().get(MetadataRouter.ROUTER_TYPE_METADATA); + Assert.assertEquals(1, metadataRouterLabels.size()); + Assert.assertEquals("NOT_EXISTED_ENV", metadataRouterLabels.get("specify-env")); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/pom.xml b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/pom.xml new file mode 100644 index 000000000..06a9c65e0 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/pom.xml @@ -0,0 +1,34 @@ + + + + spring-cloud-tencent-plugin-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-gateway-plugin + Spring Cloud Tencent Gateway Plugin + + + + org.springframework.cloud + spring-cloud-gateway-server + provided + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-config + + + + org.springframework.boot + spring-boot-starter-test + test + + + diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfiguration.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfiguration.java new file mode 100644 index 000000000..f0daf20a8 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfiguration.java @@ -0,0 +1,84 @@ +/* + * 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.plugin.gateway; + +import java.util.List; + +import com.tencent.cloud.plugin.gateway.staining.StainingProperties; +import com.tencent.cloud.plugin.gateway.staining.TrafficStainer; +import com.tencent.cloud.plugin.gateway.staining.TrafficStainingGatewayFilter; +import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingExecutor; +import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingProperties; +import com.tencent.cloud.plugin.gateway.staining.rule.RuleTrafficStainer; +import com.tencent.cloud.plugin.gateway.staining.rule.StainingRuleManager; +import com.tencent.polaris.configuration.api.core.ConfigFileService; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Auto configuration for spring cloud gateway plugins. + * @author lepdou 2022-07-06 + */ +@Configuration +@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.enabled", matchIfMissing = true) +public class SCGPluginsAutoConfiguration { + + @Configuration + @ConditionalOnProperty("spring.cloud.tencent.plugin.scg.staining.enabled") + public static class StainingPluginConfiguration { + + @Bean + public StainingProperties stainingProperties() { + return new StainingProperties(); + } + + @Configuration + @ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.staining.rule-staining.enabled", matchIfMissing = true) + public static class RuleStainingPluginConfiguration { + + @Bean + public RuleStainingProperties ruleStainingProperties() { + return new RuleStainingProperties(); + } + + @Bean + public StainingRuleManager stainingRuleManager(RuleStainingProperties stainingProperties, ConfigFileService configFileService) { + return new StainingRuleManager(stainingProperties, configFileService); + } + + @Bean + public TrafficStainingGatewayFilter trafficStainingGatewayFilter(List trafficStainer) { + return new TrafficStainingGatewayFilter(trafficStainer); + } + + @Bean + public RuleStainingExecutor ruleStainingExecutor() { + return new RuleStainingExecutor(); + } + + @Bean + public RuleTrafficStainer ruleTrafficStainer(StainingRuleManager stainingRuleManager, + RuleStainingExecutor ruleStainingExecutor) { + return new RuleTrafficStainer(stainingRuleManager, ruleStainingExecutor); + } + } + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/StainingProperties.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/StainingProperties.java new file mode 100644 index 000000000..226563bb9 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/StainingProperties.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.plugin.gateway.staining; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * The properties for traffic staining. + * @author lepdou 2022-07-07 + */ +@ConfigurationProperties("spring.cloud.tencent.plugin.scg.staining") +public class StainingProperties { + + private boolean enabled; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.java new file mode 100644 index 000000000..0eb40c3a3 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.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.plugin.gateway.staining; + +import java.util.Map; + +import org.springframework.core.Ordered; +import org.springframework.web.server.ServerWebExchange; + +/** + * Staining according to request parameters. for example, when the request parameter uid=0, staining env=blue. + * @author lepdou 2022-07-06 + */ +public interface TrafficStainer extends Ordered { + + /** + * get stained labels from request. + * @param exchange the request. + * @return stained labels. + */ + Map apply(ServerWebExchange exchange); +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilter.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilter.java new file mode 100644 index 000000000..c6ddfd457 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilter.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.plugin.gateway.staining; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.tencent.cloud.common.constant.MetadataConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.util.JacksonUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.CollectionUtils; +import org.springframework.web.server.ServerWebExchange; + +import static org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER; + +/** + * Staining the request, and the stained labels will be passed to the link through transitive metadata. + * @author lepdou 2022-07-06 + */ +public class TrafficStainingGatewayFilter implements GlobalFilter, Ordered { + + private static final Logger LOGGER = LoggerFactory.getLogger(TrafficStainingGatewayFilter.class); + + private final List trafficStainers; + + public TrafficStainingGatewayFilter(List trafficStainers) { + if (!CollectionUtils.isEmpty(trafficStainers)) { + trafficStainers.sort(Comparator.comparingInt(Ordered::getOrder)); + } + this.trafficStainers = trafficStainers; + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + if (CollectionUtils.isEmpty(trafficStainers)) { + return chain.filter(exchange); + } + + // 1. get stained labels from request + Map stainedLabels = getStainedLabels(exchange); + + if (CollectionUtils.isEmpty(stainedLabels)) { + return chain.filter(exchange); + } + + // 2. put stained labels to metadata context + ServerHttpRequest request = exchange.getRequest().mutate().headers((httpHeaders) -> { + MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT); + if (metadataContext == null) { + metadataContext = MetadataContextHolder.get(); + } + + Map oldTransitiveMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); + + // append new transitive metadata + Map newTransitiveMetadata = new HashMap<>(oldTransitiveMetadata); + newTransitiveMetadata.putAll(stainedLabels); + + metadataContext.putFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE, newTransitiveMetadata); + }).build(); + + return chain.filter(exchange.mutate().request(request).build()); + } + + Map getStainedLabels(ServerWebExchange exchange) { + Map stainedLabels = new HashMap<>(); + int size = trafficStainers.size(); + TrafficStainer stainer = null; + for (int i = size - 1; i >= 0; i--) { + try { + stainer = trafficStainers.get(i); + Map labels = stainer.apply(exchange); + if (!CollectionUtils.isEmpty(labels)) { + stainedLabels.putAll(labels); + } + } + catch (Exception e) { + if (stainer != null) { + LOGGER.error("[SCT] traffic stained error. stainer = {}", stainer.getClass().getName(), e); + } + } + } + LOGGER.debug("[SCT] traffic stained labels. {}", JacksonUtils.serialize2Json(stainedLabels)); + + return stainedLabels; + } + + @Override + public int getOrder() { + return ROUTE_TO_URL_FILTER_ORDER + 1; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutor.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutor.java new file mode 100644 index 000000000..e58df27e7 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutor.java @@ -0,0 +1,70 @@ +/* + * 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.plugin.gateway.staining.rule; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.tencent.cloud.common.rule.Condition; +import com.tencent.cloud.common.rule.ConditionUtils; +import com.tencent.cloud.common.rule.KVPairUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; + +import org.springframework.util.CollectionUtils; +import org.springframework.web.server.ServerWebExchange; + +/** + * Resolve labels from request by staining rule. + * @author lepdou 2022-07-11 + */ +public class RuleStainingExecutor { + + Map execute(ServerWebExchange exchange, StainingRule stainingRule) { + if (stainingRule == null) { + return Collections.emptyMap(); + } + + List rules = stainingRule.getRules(); + if (CollectionUtils.isEmpty(rules)) { + return Collections.emptyMap(); + } + + Map parsedLabels = new HashMap<>(); + + for (StainingRule.Rule rule : rules) { + List conditions = rule.getConditions(); + + Set keys = new HashSet<>(); + conditions.forEach(condition -> keys.add(condition.getKey())); + Map actualValues = SpringWebExpressionLabelUtils.resolve(exchange, keys); + + if (!ConditionUtils.match(actualValues, conditions)) { + continue; + } + + parsedLabels.putAll(KVPairUtils.toMap(rule.getLabels())); + } + + return parsedLabels; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingProperties.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingProperties.java new file mode 100644 index 000000000..115e2ca4e --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingProperties.java @@ -0,0 +1,73 @@ +/* + * 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.plugin.gateway.staining.rule; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * The properties for rule staining. + * @author lepdou 2022-07-11 + */ +@ConfigurationProperties("spring.cloud.tencent.plugin.scg.staining.rule-staining") +public class RuleStainingProperties { + + @Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.namespace:${spring.cloud.tencent.namespace:default}}") + private String namespace; + + @Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.group:${spring.application.name:spring-cloud-gateway}}") + private String group; + + @Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.fileName:rule/staining.json}") + private String fileName; + + private boolean enabled = true; + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainer.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainer.java new file mode 100644 index 000000000..f96ec2a70 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainer.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.plugin.gateway.staining.rule; + +import java.util.Collections; +import java.util.Map; + +import com.tencent.cloud.plugin.gateway.staining.TrafficStainer; + +import org.springframework.web.server.ServerWebExchange; + +/** + * Staining the request by staining rules. + * @author lepdou 2022-07-06 + */ +public class RuleTrafficStainer implements TrafficStainer { + + private final StainingRuleManager stainingRuleManager; + private final RuleStainingExecutor ruleStainingExecutor; + + public RuleTrafficStainer(StainingRuleManager stainingRuleManager, RuleStainingExecutor ruleStainingExecutor) { + this.stainingRuleManager = stainingRuleManager; + this.ruleStainingExecutor = ruleStainingExecutor; + } + + @Override + public Map apply(ServerWebExchange exchange) { + StainingRule stainingRule = stainingRuleManager.getStainingRule(); + + if (stainingRule == null) { + return Collections.emptyMap(); + } + + return ruleStainingExecutor.execute(exchange, stainingRule); + } + + @Override + public int getOrder() { + return 0; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRule.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRule.java new file mode 100644 index 000000000..d76145659 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRule.java @@ -0,0 +1,78 @@ +/* + * 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.plugin.gateway.staining.rule; + +import java.util.List; + +import com.tencent.cloud.common.rule.Condition; +import com.tencent.cloud.common.rule.KVPair; + +/** + * The rules for staining. + * @author lepdou 2022-07-07 + */ +public class StainingRule { + + private List rules; + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + + @Override + public String toString() { + return "StainingRule{" + + "rules=" + rules + + '}'; + } + + public static class Rule { + private List conditions; + private List labels; + + public List getConditions() { + return conditions; + } + + public void setConditions(List conditions) { + this.conditions = conditions; + } + + public List getLabels() { + return labels; + } + + public void setLabels(List labels) { + this.labels = labels; + } + + @Override + public String toString() { + return "Rule{" + + "conditions=" + conditions + + ", labels=" + labels + + '}'; + } + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManager.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManager.java new file mode 100644 index 000000000..2ecb0cce6 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManager.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.plugin.gateway.staining.rule; + +import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.polaris.configuration.api.core.ConfigFile; +import com.tencent.polaris.configuration.api.core.ConfigFileService; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Fetch staining rule from polaris, and deserialize to {@link StainingRule}. + * @author lepdou 2022-07-07 + */ +public class StainingRuleManager { + private static final Logger LOGGER = LoggerFactory.getLogger(StainingRuleManager.class); + + private final RuleStainingProperties stainingProperties; + private final ConfigFileService configFileService; + + private StainingRule stainingRule; + + public StainingRuleManager(RuleStainingProperties stainingProperties, ConfigFileService configFileService) { + this.stainingProperties = stainingProperties; + this.configFileService = configFileService; + + initStainingRule(); + } + + private void initStainingRule() { + ConfigFile rulesFile = configFileService.getConfigFile(stainingProperties.getNamespace(), stainingProperties.getGroup(), + stainingProperties.getFileName()); + + rulesFile.addChangeListener(event -> { + LOGGER.info("[SCT] update scg staining rules. {}", event); + deserialize(event.getNewValue()); + }); + + String ruleJson = rulesFile.getContent(); + LOGGER.info("[SCT] init scg staining rules. {}", ruleJson); + + deserialize(ruleJson); + } + + private void deserialize(String ruleJsonStr) { + if (StringUtils.isBlank(ruleJsonStr)) { + stainingRule = null; + return; + } + + try { + stainingRule = JacksonUtils.deserialize(ruleJsonStr, StainingRule.class); + } + catch (Exception e) { + LOGGER.error("[SCT] deserialize staining rule error.", e); + throw e; + } + } + + public StainingRule getStainingRule() { + return stainingRule; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 000000000..a54b4b3fb --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,40 @@ +{ + "properties": [ + { + "name": "spring.cloud.tencent.plugin.scg.enabled", + "type": "java.lang.Boolean", + "defaultValue": true, + "description": "the switch for spring cloud gateway plugin." + }, + { + "name": "spring.cloud.tencent.plugin.scg.staining.enabled", + "type": "java.lang.Boolean", + "defaultValue": false, + "description": "the switch for spring cloud gateway staining plugin." + }, + { + "name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.enabled", + "type": "java.lang.Boolean", + "defaultValue": true, + "description": "the switch for spring cloud gateway rule staining plugin." + }, + { + "name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.namespace", + "type": "java.lang.String", + "defaultValue": "default", + "description": "The namespace used to config staining rules." + }, + { + "name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.group", + "type": "java.lang.String", + "defaultValue": "${spring.application.name}", + "description": "The group used to config staining rules." + }, + { + "name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.fileName", + "type": "java.lang.String", + "defaultValue": "rule/staining.json", + "description": "The file name used to config staining rules." + } + ] +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..70c95961b --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.plugin.gateway.SCGPluginsAutoConfiguration diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainerGatewayFilterTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainerGatewayFilterTest.java new file mode 100644 index 000000000..6339691db --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainerGatewayFilterTest.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.plugin.gateway.staining; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.util.CollectionUtils; +import org.springframework.web.server.ServerWebExchange; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test for {@link TrafficStainingGatewayFilter}. + * @author lepdou 2022-07-12 + */ +@RunWith(MockitoJUnitRunner.class) +public class TrafficStainerGatewayFilterTest { + + @Mock + private GatewayFilterChain chain; + @Mock + private ServerWebExchange exchange; + + @Test + public void testNoneTrafficStainingImplement() { + TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(null); + + when(chain.filter(exchange)).thenReturn(Mono.empty()); + + filter.filter(exchange, chain); + + verify(chain).filter(exchange); + } + + @Test + public void testMultiStaining() { + TrafficStainer trafficStainer1 = Mockito.mock(TrafficStainer.class); + TrafficStainer trafficStainer2 = Mockito.mock(TrafficStainer.class); + + when(trafficStainer1.getOrder()).thenReturn(1); + when(trafficStainer2.getOrder()).thenReturn(2); + + Map labels1 = new HashMap<>(); + labels1.put("k1", "v1"); + labels1.put("k2", "v2"); + when(trafficStainer1.apply(exchange)).thenReturn(labels1); + + Map labels2 = new HashMap<>(); + labels2.put("k1", "v11"); + labels2.put("k3", "v3"); + when(trafficStainer2.apply(exchange)).thenReturn(labels2); + + TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(Arrays.asList(trafficStainer1, trafficStainer2)); + Map result = filter.getStainedLabels(exchange); + + Assert.assertFalse(CollectionUtils.isEmpty(result)); + Assert.assertEquals("v1", result.get("k1")); + Assert.assertEquals("v2", result.get("k2")); + Assert.assertEquals("v3", result.get("k3")); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutorTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutorTest.java new file mode 100644 index 000000000..91c0c42d8 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutorTest.java @@ -0,0 +1,181 @@ +/* + * 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.plugin.gateway.staining.rule; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import com.tencent.cloud.common.rule.Condition; +import com.tencent.cloud.common.rule.KVPair; +import com.tencent.cloud.common.rule.Operation; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; + +/** + * Test for {@link RuleStainingExecutor}. + * @author lepdou 2022-07-12 + */ +@RunWith(MockitoJUnitRunner.class) +public class RuleStainingExecutorTest { + + @Test + public void testMatchCondition() { + Condition condition1 = new Condition(); + condition1.setKey("${http.header.uid}"); + condition1.setOperation(Operation.EQUALS.toString()); + condition1.setValues(Collections.singletonList("1000")); + + Condition condition2 = new Condition(); + condition2.setKey("${http.query.source}"); + condition2.setOperation(Operation.IN.toString()); + condition2.setValues(Collections.singletonList("wx")); + + StainingRule.Rule rule = new StainingRule.Rule(); + rule.setConditions(Arrays.asList(condition1, condition2)); + + KVPair kvPair = new KVPair(); + kvPair.setKey("env"); + kvPair.setValue("blue"); + rule.setLabels(Collections.singletonList(kvPair)); + + StainingRule stainingRule = new StainingRule(); + stainingRule.setRules(Collections.singletonList(rule)); + + MockServerHttpRequest request = MockServerHttpRequest.get("/users") + .queryParam("source", "wx") + .header("uid", "1000").build(); + MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build(); + + RuleStainingExecutor executor = new RuleStainingExecutor(); + + Map stainedLabels = executor.execute(exchange, stainingRule); + + Assert.assertNotNull(stainedLabels); + Assert.assertEquals(1, stainedLabels.size()); + Assert.assertEquals("blue", stainedLabels.get("env")); + } + + @Test + public void testNotMatchCondition() { + Condition condition1 = new Condition(); + condition1.setKey("${http.header.uid}"); + condition1.setOperation(Operation.EQUALS.toString()); + condition1.setValues(Collections.singletonList("1000")); + + Condition condition2 = new Condition(); + condition2.setKey("${http.query.source}"); + condition2.setOperation(Operation.IN.toString()); + condition2.setValues(Collections.singletonList("wx")); + + StainingRule.Rule rule = new StainingRule.Rule(); + rule.setConditions(Arrays.asList(condition1, condition2)); + + KVPair kvPair = new KVPair(); + kvPair.setKey("env"); + kvPair.setValue("blue"); + rule.setLabels(Collections.singletonList(kvPair)); + + StainingRule stainingRule = new StainingRule(); + stainingRule.setRules(Collections.singletonList(rule)); + + MockServerHttpRequest request = MockServerHttpRequest.get("/users") + .queryParam("source", "wx") + .header("uid", "10001").build(); + MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build(); + + RuleStainingExecutor executor = new RuleStainingExecutor(); + + Map stainedLabels = executor.execute(exchange, stainingRule); + + Assert.assertNotNull(stainedLabels); + Assert.assertEquals(0, stainedLabels.size()); + } + + @Test + public void testMatchTwoRulesAndNotMatchOneRule() { + Condition condition1 = new Condition(); + condition1.setKey("${http.header.uid}"); + condition1.setOperation(Operation.EQUALS.toString()); + condition1.setValues(Collections.singletonList("1000")); + + Condition condition2 = new Condition(); + condition2.setKey("${http.query.source}"); + condition2.setOperation(Operation.IN.toString()); + condition2.setValues(Collections.singletonList("wx")); + + // rule1 matched + StainingRule.Rule rule1 = new StainingRule.Rule(); + rule1.setConditions(Arrays.asList(condition1, condition2)); + + KVPair kvPair = new KVPair(); + kvPair.setKey("env"); + kvPair.setValue("blue"); + rule1.setLabels(Collections.singletonList(kvPair)); + + // rule2 matched + StainingRule.Rule rule2 = new StainingRule.Rule(); + rule2.setConditions(Collections.singletonList(condition1)); + + KVPair kvPair2 = new KVPair(); + kvPair2.setKey("label1"); + kvPair2.setValue("value1"); + KVPair kvPair3 = new KVPair(); + kvPair3.setKey("label2"); + kvPair3.setValue("value2"); + rule2.setLabels(Arrays.asList(kvPair2, kvPair3)); + + // rule3 not matched + Condition condition3 = new Condition(); + condition3.setKey("${http.query.type}"); + condition3.setOperation(Operation.IN.toString()); + condition3.setValues(Collections.singletonList("wx")); + + StainingRule.Rule rule3 = new StainingRule.Rule(); + rule3.setConditions(Collections.singletonList(condition3)); + + KVPair kvPair4 = new KVPair(); + kvPair4.setKey("label3"); + kvPair4.setValue("value3"); + rule3.setLabels(Collections.singletonList(kvPair4)); + + StainingRule stainingRule = new StainingRule(); + stainingRule.setRules(Arrays.asList(rule1, rule2, rule3)); + + MockServerHttpRequest request = MockServerHttpRequest.get("/users") + .queryParam("source", "wx") + .header("uid", "1000").build(); + MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build(); + + RuleStainingExecutor executor = new RuleStainingExecutor(); + + Map stainedLabels = executor.execute(exchange, stainingRule); + + Assert.assertNotNull(stainedLabels); + Assert.assertEquals(3, stainedLabels.size()); + Assert.assertEquals("blue", stainedLabels.get("env")); + Assert.assertEquals("value1", stainedLabels.get("label1")); + Assert.assertEquals("value2", stainedLabels.get("label2")); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManagerTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManagerTest.java new file mode 100644 index 000000000..1ac88e3c8 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManagerTest.java @@ -0,0 +1,133 @@ +/* + * 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.plugin.gateway.staining.rule; + +import com.tencent.polaris.configuration.api.core.ConfigFile; +import com.tencent.polaris.configuration.api.core.ConfigFileService; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.Mockito.when; + + +/** + * Test for {@link StainingRuleManager}. + * @author lepdou 2022-07-12 + */ +@RunWith(MockitoJUnitRunner.class) +public class StainingRuleManagerTest { + + @Mock + private ConfigFileService configFileService; + + private final String testNamespace = "testNamespace"; + private final String testGroup = "testGroup"; + private final String testFileName = "rule.json"; + + @Test + public void testNormalRule() { + RuleStainingProperties ruleStainingProperties = new RuleStainingProperties(); + ruleStainingProperties.setNamespace(testNamespace); + ruleStainingProperties.setGroup(testGroup); + ruleStainingProperties.setFileName(testFileName); + + ConfigFile configFile = Mockito.mock(ConfigFile.class); + when(configFile.getContent()).thenReturn("{\n" + + " \"rules\":[\n" + + " {\n" + + " \"conditions\":[\n" + + " {\n" + + " \"key\":\"${http.query.uid}\",\n" + + " \"values\":[\"1000\"],\n" + + " \"operation\":\"EQUAL\"\n" + + " }\n" + + " ],\n" + + " \"labels\":[\n" + + " {\n" + + " \"key\":\"env\",\n" + + " \"value\":\"blue\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"); + when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile); + + StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService); + + StainingRule stainingRule = stainingRuleManager.getStainingRule(); + + Assert.assertNotNull(stainingRule); + Assert.assertEquals(1, stainingRule.getRules().size()); + StainingRule.Rule rule = stainingRule.getRules().get(0); + Assert.assertEquals(1, rule.getConditions().size()); + Assert.assertEquals(1, rule.getLabels().size()); + } + + @Test(expected = RuntimeException.class) + public void testWrongRule() { + RuleStainingProperties ruleStainingProperties = new RuleStainingProperties(); + ruleStainingProperties.setNamespace(testNamespace); + ruleStainingProperties.setGroup(testGroup); + ruleStainingProperties.setFileName(testFileName); + + ConfigFile configFile = Mockito.mock(ConfigFile.class); + when(configFile.getContent()).thenReturn("{\n" + + " \"rules\":[\n" + + " {\n" + + " \"conditionsxxxx\":[\n" + + " {\n" + + " \"key\":\"${http.query.uid}\",\n" + + " \"values\":[\"1000\"],\n" + + " \"operation\":\"EQUAL\"\n" + + " }\n" + + " ],\n" + + " \"labels\":[\n" + + " {\n" + + " \"key\":\"env\",\n" + + " \"value\":\"blue\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"); + when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile); + + new StainingRuleManager(ruleStainingProperties, configFileService); + } + + @Test + public void testEmptyRule() { + RuleStainingProperties ruleStainingProperties = new RuleStainingProperties(); + ruleStainingProperties.setNamespace(testNamespace); + ruleStainingProperties.setGroup(testGroup); + ruleStainingProperties.setFileName(testFileName); + + ConfigFile configFile = Mockito.mock(ConfigFile.class); + when(configFile.getContent()).thenReturn(null); + when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile); + + StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService); + Assert.assertNull(stainingRuleManager.getStainingRule()); + } +}