From 8f0971efcdb6571d0524cdd84bccd970164c6309 Mon Sep 17 00:00:00 2001 From: lepdou Date: Thu, 14 Jul 2022 13:09:31 +0800 Subject: [PATCH] add feature-env plugin & add spring cloud gateway staining plugin (#428) --- CHANGELOG.md | 1 + pom.xml | 1 + .../CustomTransitiveMetadataResolver.java | 36 +- .../core/EncodeTransferMedataScgFilter.java | 3 +- .../CustomTransitiveMetadataResolverTest.java | 23 +- .../ratelimit/RateLimitRuleLabelResolver.java | 2 +- .../filter/QuotaCheckReactiveFilter.java | 4 +- .../filter/QuotaCheckServletFilter.java | 4 +- .../filter/QuotaCheckReactiveFilterTest.java | 8 +- .../filter/QuotaCheckServletFilterTest.java | 8 +- .../PolarisLoadBalancerCompositeRule.java | 110 +++--- .../polaris/router/PolarisRouterContext.java | 61 +++- .../router/RouterRuleLabelResolver.java | 2 +- .../router/config/RibbonConfiguration.java | 18 +- .../config/RouterAutoConfiguration.java | 22 ++ .../feign/FeignExpressionLabelUtils.java | 2 +- .../feign/PolarisFeignLoadBalancer.java | 4 +- .../MetadataRouterRequestInterceptor.java | 57 ++++ .../NearbyRouterRequestInterceptor.java | 53 +++ .../RuleBasedRouterRequestInterceptor.java | 59 ++++ .../PolarisLoadBalancerInterceptor.java | 8 +- .../scg/PolarisLoadBalancerClientFilter.java | 8 +- .../router/spi/RouterRequestInterceptor.java | 37 ++ .../router/spi/RouterResponseInterceptor.java | 38 +++ .../PolarisLoadBalancerCompositeRuleTest.java | 65 ++-- .../router/PolarisRouterContextTest.java | 67 +++- .../feign/PolarisFeignLoadBalancerTest.java | 2 +- .../PolarisLoadBalancerInterceptorTest.java | 10 +- .../PolarisLoadBalancerClientFilterTest.java | 2 +- .../common/constant/MetadataConstant.java | 21 ++ .../config/MetadataAutoConfiguration.java | 15 - .../gateway/MetadataFirstScgFilter.java | 62 ---- .../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 | 319 ------------------ .../cloud/common/util/JacksonUtils.java | 10 + .../expresstion/ExpressionLabelUtils.java | 136 ++++++++ .../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 | 15 +- .../gateway-callee-service/pom.xml | 7 +- .../src/main/resources/bootstrap.yml | 2 - .../gateway-callee-service2/pom.xml | 5 + .../gateway-scg-service/pom.xml | 10 + .../src/main/resources/bootstrap.yml | 14 +- spring-cloud-tencent-plugin-starters/pom.xml | 22 ++ .../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 | 104 ++++++ .../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 ++++++++ 75 files changed, 2897 insertions(+), 582 deletions(-) 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 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-plugin-starters/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 e02220c7..b44658e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - [Bugfix: optimize ratelimit actuator](https://github.com/Tencent/spring-cloud-tencent/pull/413) - [Feature: add rate limit filter debug log](https://github.com/Tencent/spring-cloud-tencent/pull/417) +- [Feature: add feature-env plugin & add spring cloud gateway staining plugin](https://github.com/Tencent/spring-cloud-tencent/pull/428) diff --git a/pom.xml b/pom.xml index d13aac07..0839b719 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,7 @@ spring-cloud-starter-tencent-polaris-ratelimit spring-cloud-starter-tencent-polaris-circuitbreaker spring-cloud-starter-tencent-polaris-router + spring-cloud-tencent-plugin-starters spring-cloud-tencent-dependencies spring-cloud-tencent-examples spring-cloud-tencent-coverage 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 b4303f6f..f37ff140 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; @@ -38,9 +39,6 @@ import org.springframework.web.server.ServerWebExchange; */ 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() { } @@ -50,10 +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)); + } + + //resolve polaris transitive header + if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX) && !CollectionUtils.isEmpty(entry.getValue())) { - String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH); + String sourceKey = StringUtils.substring(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH); result.put(sourceKey, entry.getValue().get(0)); } } @@ -67,11 +75,21 @@ public final class CustomTransitiveMetadataResolver { Enumeration headers = request.getHeaderNames(); while (headers.hasMoreElements()) { String key = headers.nextElement(); + if (StringUtils.isBlank(key)) { + continue; + } + + // 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)); + } - if (StringUtils.isNotBlank(key) - && StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX) + // resolve polaris transitive header + if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX) && StringUtils.isNotBlank(request.getHeader(key))) { - String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH); + 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/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java index e6c2946e..f8274b50 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java @@ -59,11 +59,10 @@ public class EncodeTransferMedataScgFilter implements GlobalFilter, Ordered { // get metadata of current thread MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT); - - // add new metadata and cover old if (metadataContext == null) { metadataContext = MetadataContextHolder.get(); } + Map customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); if (!CollectionUtils.isEmpty(customMetadata)) { String metadataStr = JacksonUtils.serialize2Json(customMetadata); 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 9dc83bd5..a1b28645 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 bee50dc5..dc61665c 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 cdca4822..cb95857b 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 8497bdf4..82c3d791 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 b54ffec6..9e2da2c1 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 cd68a8fd..05d490d0 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 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); diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java index 4be83e68..f3763a13 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java @@ -19,10 +19,7 @@ package com.tencent.cloud.polaris.router; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; @@ -40,15 +37,11 @@ import com.tencent.cloud.common.pojo.PolarisServer; import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils; import com.tencent.cloud.polaris.loadbalancer.PolarisWeightedRule; import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties; -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.spi.RouterRequestInterceptor; +import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor; 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; @@ -82,24 +75,21 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule { final static String STRATEGY_AVAILABILITY_FILTERING = "availabilityFilteringRule"; private final PolarisLoadBalancerProperties loadBalancerProperties; - private final PolarisNearByRouterProperties polarisNearByRouterProperties; - private final PolarisMetadataRouterProperties polarisMetadataRouterProperties; - private final PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties; private final RouterAPI routerAPI; + private final List requestInterceptors; + private final List responseInterceptors; private final AbstractLoadBalancerRule delegateRule; public PolarisLoadBalancerCompositeRule(RouterAPI routerAPI, PolarisLoadBalancerProperties polarisLoadBalancerProperties, - PolarisNearByRouterProperties polarisNearByRouterProperties, - PolarisMetadataRouterProperties polarisMetadataRouterProperties, - PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties, - IClientConfig iClientConfig) { + IClientConfig iClientConfig, + List requestInterceptors, + List responseInterceptors) { this.routerAPI = routerAPI; - this.polarisNearByRouterProperties = polarisNearByRouterProperties; this.loadBalancerProperties = polarisLoadBalancerProperties; - this.polarisMetadataRouterProperties = polarisMetadataRouterProperties; - this.polarisRuleBasedRouterProperties = polarisRuleBasedRouterProperties; + this.requestInterceptors = requestInterceptors; + this.responseInterceptors = responseInterceptors; delegateRule = getRule(); delegateRule.initWithNiwsConfig(iClientConfig); @@ -118,80 +108,66 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule { } // 2. filter by router - List serversAfterRouter = doRouter(allServers, key); - - // 3. filter by load balance. - // A LoadBalancer needs to be regenerated for each request, - // because the list of servers may be different after filtered by router - ILoadBalancer loadBalancer = new SimpleLoadBalancer(); - loadBalancer.addServers(serversAfterRouter); - delegateRule.setLoadBalancer(loadBalancer); + if (key instanceof PolarisRouterContext) { + PolarisRouterContext routerContext = (PolarisRouterContext) key; + List serversAfterRouter = doRouter(allServers, routerContext); + // 3. filter by load balance. + // A LoadBalancer needs to be regenerated for each request, + // because the list of servers may be different after filtered by router + ILoadBalancer loadBalancer = new SimpleLoadBalancer(); + loadBalancer.addServers(serversAfterRouter); + delegateRule.setLoadBalancer(loadBalancer); + } return delegateRule.choose(key); } - List doRouter(List allServers, Object key) { + List doRouter(List allServers, PolarisRouterContext routerContext) { ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(allServers); - // filter instance by routers - ProcessRoutersRequest processRoutersRequest = buildProcessRoutersRequest(serviceInstances, key); + ProcessRoutersRequest processRoutersRequest = buildProcessRoutersBaseRequest(serviceInstances); + // process request interceptors + processRouterRequestInterceptors(processRoutersRequest, routerContext); + + // process router chain ProcessRoutersResponse processRoutersResponse = routerAPI.processRouters(processRoutersRequest); - List filteredInstances = new ArrayList<>(); + // process response interceptors + processRouterResponseInterceptors(routerContext, processRoutersResponse); + + // transfer polaris server to ribbon server ServiceInstances filteredServiceInstances = processRoutersResponse.getServiceInstances(); + List filteredInstances = new ArrayList<>(); for (Instance instance : filteredServiceInstances.getInstances()) { filteredInstances.add(new PolarisServer(serviceInstances, instance)); } + return filteredInstances; } - ProcessRoutersRequest buildProcessRoutersRequest(ServiceInstances serviceInstances, Object key) { + ProcessRoutersRequest buildProcessRoutersBaseRequest(ServiceInstances serviceInstances) { 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); - - 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); - } - processRoutersRequest.setSourceService(serviceInfo); - return processRoutersRequest; } - private Map getRouterLabels(Object key, String type) { - if (key instanceof PolarisRouterContext) { - return ((PolarisRouterContext) key).getLabels(type); + void processRouterRequestInterceptors(ProcessRoutersRequest processRoutersRequest, PolarisRouterContext routerContext) { + for (RouterRequestInterceptor requestInterceptor : requestInterceptors) { + requestInterceptor.apply(processRoutersRequest, routerContext); + } + } + + private void processRouterResponseInterceptors(PolarisRouterContext routerContext, ProcessRoutersResponse processRoutersResponse) { + if (!CollectionUtils.isEmpty(responseInterceptors)) { + for (RouterResponseInterceptor responseInterceptor : responseInterceptors) { + responseInterceptor.apply(processRoutersResponse, routerContext); + } } - return Collections.emptyMap(); } public AbstractLoadBalancerRule getRule() { 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 1e05d2ec..b2569bd1 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,9 +18,14 @@ 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; @@ -30,15 +35,14 @@ import org.springframework.util.CollectionUtils; * @author lepdou 2022-05-17 */ 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,7 +57,54 @@ 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<>(); } 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 ded1784f..f0df59de 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/config/RibbonConfiguration.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RibbonConfiguration.java index e8fe2f06..79cc513e 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RibbonConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RibbonConfiguration.java @@ -18,13 +18,14 @@ package com.tencent.cloud.polaris.router.config; +import java.util.List; + import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.IRule; import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties; import com.tencent.cloud.polaris.router.PolarisLoadBalancerCompositeRule; -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.spi.RouterRequestInterceptor; +import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor; import com.tencent.polaris.router.api.core.RouterAPI; import org.springframework.context.annotation.Bean; @@ -39,12 +40,9 @@ public class RibbonConfiguration { @Bean public IRule polarisLoadBalancerCompositeRule(RouterAPI routerAPI, PolarisLoadBalancerProperties polarisLoadBalancerProperties, - PolarisNearByRouterProperties polarisNearByRouterProperties, - PolarisMetadataRouterProperties polarisMetadataRouterProperties, - PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties, - IClientConfig iClientConfig) { - return new PolarisLoadBalancerCompositeRule(routerAPI, polarisLoadBalancerProperties, - polarisNearByRouterProperties, polarisMetadataRouterProperties, - polarisRuleBasedRouterProperties, iClientConfig); + IClientConfig iClientConfig, List requestInterceptors, + List responseInterceptors) { + return new PolarisLoadBalancerCompositeRule(routerAPI, polarisLoadBalancerProperties, iClientConfig, + 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 6364c050..0ac1b702 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 @@ -25,8 +25,12 @@ import com.tencent.cloud.polaris.router.beanprocessor.LoadBalancerInterceptorBea 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.netflix.ribbon.RibbonClients; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -63,4 +67,22 @@ public class RouterAutoConfiguration { 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/feign/FeignExpressionLabelUtils.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java index c3d19b35..b040a6f7 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/PolarisFeignLoadBalancer.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancer.java index b57bf30c..618d6c2f 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancer.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancer.java @@ -71,7 +71,7 @@ public class PolarisFeignLoadBalancer extends FeignLoadBalancer { 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<>(); @@ -86,7 +86,7 @@ public class PolarisFeignLoadBalancer extends FeignLoadBalancer { 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; } 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 00000000..9b9a78f2 --- /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 00000000..86efcd62 --- /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 00000000..9abba566 --- /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 ddb21514..c427075c 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 @@ -30,7 +30,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.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; import com.tencent.cloud.polaris.router.PolarisRouterContext; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.spi.RouterLabelResolver; @@ -137,8 +137,8 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor { PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels); - routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); return routerContext; } @@ -151,6 +151,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/PolarisLoadBalancerClientFilter.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilter.java index c46bc6e5..c610a0c9 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilter.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilter.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.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; import com.tencent.cloud.polaris.router.PolarisRouterContext; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.spi.RouterLabelResolver; @@ -126,8 +126,8 @@ public class PolarisLoadBalancerClientFilter extends LoadBalancerClientFilter { PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels); - routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); return routerContext; } @@ -140,6 +140,6 @@ public class PolarisLoadBalancerClientFilter extends LoadBalancerClientFilter { 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 00000000..6e515d9c --- /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 00000000..5d80d0c2 --- /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/PolarisLoadBalancerCompositeRuleTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRuleTest.java index 4caf5d08..19cde939 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRuleTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRuleTest.java @@ -18,6 +18,7 @@ package com.tencent.cloud.polaris.router; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -44,6 +45,10 @@ import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperti 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; @@ -87,23 +92,28 @@ public class PolarisLoadBalancerCompositeRuleTest { private PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties; @Mock private RouterAPI routerAPI; + private IClientConfig config; private String testNamespace = "testNamespace"; private String testCallerService = "testCallerService"; private String testCalleeService = "testCalleeService"; + private final List requestInterceptors = new ArrayList<>(); + @Before public void before() { config = new DefaultClientConfigImpl(); config.loadDefaultValues(); + requestInterceptors.add(new MetadataRouterRequestInterceptor(polarisMetadataRouterProperties)); + requestInterceptors.add(new NearbyRouterRequestInterceptor(polarisNearByRouterProperties)); + requestInterceptors.add(new RuleBasedRouterRequestInterceptor(polarisRuleBasedRouterProperties)); } @Test public void testGetDefaultLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(""); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule defaultRule = compositeRule.getRule(); @@ -114,8 +124,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testRandomLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RANDOM); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -126,8 +135,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testWeightLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_WEIGHT); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -138,8 +146,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testRetryLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RETRY); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -150,8 +157,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testWeightedResponseTimeLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RESPONSE_TIME_WEIGHTED); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -162,8 +168,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void tesBestAvailableLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_BEST_AVAILABLE); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -174,8 +179,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void tesRoundRobinLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_ROUND_ROBIN); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -186,8 +190,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testAvailabilityFilteringLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_AVAILABILITY_FILTERING); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -206,13 +209,18 @@ public class PolarisLoadBalancerCompositeRuleTest { setTransitiveMetadata(); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, 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 = compositeRule.buildProcessRoutersBaseRequest(serviceInstances); + compositeRule.processRouterRequestInterceptors(request, routerContext); Map routerMetadata = request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA); @@ -236,13 +244,13 @@ public class PolarisLoadBalancerCompositeRuleTest { setTransitiveMetadata(); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); ServiceInstances serviceInstances = assembleServiceInstances(); PolarisRouterContext routerContext = assembleRouterContext(); - ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext); + ProcessRoutersRequest request = compositeRule.buildProcessRoutersBaseRequest(serviceInstances); + compositeRule.processRouterRequestInterceptors(request, routerContext); Map routerMetadata = request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY); @@ -267,13 +275,13 @@ public class PolarisLoadBalancerCompositeRuleTest { setTransitiveMetadata(); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); ServiceInstances serviceInstances = assembleServiceInstances(); PolarisRouterContext routerContext = assembleRouterContext(); - ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext); + ProcessRoutersRequest request = compositeRule.buildProcessRoutersBaseRequest(serviceInstances); + compositeRule.processRouterRequestInterceptors(request, routerContext); Map routerMetadata = request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED); @@ -298,8 +306,7 @@ public class PolarisLoadBalancerCompositeRuleTest { setTransitiveMetadata(); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); ProcessRoutersResponse assembleResponse = assembleProcessRoutersResponse(); when(routerAPI.processRouters(any())).thenReturn(assembleResponse); @@ -339,8 +346,8 @@ public class PolarisLoadBalancerCompositeRuleTest { 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/PolarisRouterContextTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java index 0f209423..ced54a59 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/feign/PolarisFeignLoadBalancerTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancerTest.java index 5feda8a9..930abe89 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancerTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancerTest.java @@ -85,7 +85,7 @@ public class PolarisFeignLoadBalancerTest { PolarisRouterContext routerContext = polarisFeignLoadBalancer.buildRouterContext(headers); Assert.assertNotNull(routerContext); - Map routerLabels = routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS); + Map routerLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS); Assert.assertNotNull(routerLabels); Assert.assertEquals("v1", routerLabels.get("k1")); Assert.assertEquals("v2", routerLabels.get("k2")); 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 0514c5b6..66bd4fe5 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 @@ -210,11 +210,11 @@ public class PolarisLoadBalancerInterceptorTest { Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).get("k1")); Assert.assertEquals("v22", routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).get("k2")); - Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k1")); - Assert.assertEquals("v22", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k2")); - Assert.assertEquals("v4", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k4")); - Assert.assertEquals("GET", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("${http.method}")); - Assert.assertEquals("/user/get", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("${http.uri}")); + Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k1")); + Assert.assertEquals("v22", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k2")); + Assert.assertEquals("v4", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k4")); + Assert.assertEquals("GET", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("${http.method}")); + Assert.assertEquals("/user/get", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("${http.uri}")); } } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilterTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilterTest.java index b4c22b04..469f9d70 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilterTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilterTest.java @@ -128,7 +128,7 @@ public class PolarisLoadBalancerClientFilterTest { PolarisRouterContext routerContext = polarisLoadBalancerClientFilter.genRouterContext(webExchange, calleeService); - Map routerLabels = routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS); + Map routerLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS); Assert.assertEquals("v1", routerLabels.get("${http.header.k1}")); Assert.assertEquals("zhangsan", routerLabels.get("${http.query.userid}")); Assert.assertEquals("blue", routerLabels.get("env")); 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 09e17e1e..89f2c79d 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java @@ -26,6 +26,27 @@ import org.springframework.core.Ordered; */ public final class MetadataConstant { + /** + * 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 a8a5d59a..060bed30 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 - @ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.GlobalFilter") - 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 e73c2bf7..00000000 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/filter/gateway/MetadataFirstScgFilter.java +++ /dev/null @@ -1,62 +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 00000000..6823594e --- /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 00000000..6092c051 --- /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 00000000..cd722fa1 --- /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 00000000..11edcefd --- /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 00000000..1d32638f --- /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. + */ + EQUAL("EQUAL"), + /** + * case sensitive string not equals. + */ + NOT_EQUAL("NOT_EQUAL"), + /** + * 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 EQUAL: + return firstExpectedValue != null && StringUtils.equals(actualValue, firstExpectedValue); + case NOT_EQUAL: + 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, EQUAL.value)) { + return EQUAL; + } + if (StringUtils.equalsIgnoreCase(operation, NOT_EQUAL.value)) { + return NOT_EQUAL; + } + 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 dde5bc25..00000000 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java +++ /dev/null @@ -1,319 +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 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 suffix of expression. - */ - public static final String LABEL_SUFFIX = "}"; - private ExpressionLabelUtils() { - } - - public static boolean isExpressionLabel(String labelKey) { - if (StringUtils.isEmpty(labelKey)) { - return false; - } - if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey) || - StringUtils.startsWithIgnoreCase(LABEL_URI, labelKey)) { - return true; - } - return (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX) || - StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX) || - StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_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 4ed4d2ea..c693fef2 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 @@ -61,6 +61,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 00000000..f42c5b10 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.java @@ -0,0 +1,136 @@ +/* + * 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 suffix of expression. + */ + public static final String LABEL_SUFFIX = "}"; + + private ExpressionLabelUtils() { + } + + public static boolean isExpressionLabel(String labelKey) { + if (StringUtils.isEmpty(labelKey)) { + return false; + } + if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey) || + StringUtils.startsWithIgnoreCase(LABEL_URI, labelKey)) { + return true; + } + return (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX) || + StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX) || + StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_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 00000000..cf504fff --- /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 00000000..ebf27607 --- /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 e73ee3a2..75ba3d99 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; @@ -50,9 +49,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); }); } @@ -65,9 +61,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); }); } @@ -80,9 +73,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 00000000..c5ab0cd6 --- /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.EQUAL.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "v2", Operation.EQUAL.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList(""), "v2", Operation.EQUAL.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "", Operation.EQUAL.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), null, Operation.EQUAL.getValue())); + Assert.assertFalse(Operation.match(Collections.emptyList(), "v1", Operation.EQUAL.getValue())); + } + + @Test + public void testNotEqual() { + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "v1", Operation.NOT_EQUAL.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "v2", Operation.NOT_EQUAL.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList(""), "v2", Operation.NOT_EQUAL.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "", Operation.NOT_EQUAL.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), null, Operation.NOT_EQUAL.getValue())); + Assert.assertTrue(Operation.match(Collections.emptyList(), "v1", Operation.NOT_EQUAL.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 76666482..2e6f13ec 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 37903c14..077ee0c3 100644 --- a/spring-cloud-tencent-coverage/pom.xml +++ b/spring-cloud-tencent-coverage/pom.xml @@ -63,6 +63,16 @@ com.tencent.cloud spring-cloud-starter-tencent-polaris-config + + + com.tencent.cloud + spring-cloud-tencent-featureenv-plugin + + + + com.tencent.cloud + spring-cloud-tencent-gateway-plugin + diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index 2027ffae..02bbd20f 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -71,7 +71,7 @@ 1.7.0-Hoxton.SR12-SNAPSHOT - 1.7.0 + 1.7.1-SNAPSHOT 1.2.11 4.5.1 1.12.10 @@ -151,6 +151,19 @@ ${revision} + + + com.tencent.cloud + spring-cloud-tencent-featureenv-plugin + ${revision} + + + + com.tencent.cloud + spring-cloud-tencent-gateway-plugin + ${revision} + + com.google.guava 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 4cb4412c..cc37a455 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,9 +19,14 @@ spring-cloud-starter-tencent-polaris-discovery + + com.tencent.cloud + spring-cloud-starter-tencent-metadata-transfer + + org.springframework.boot spring-boot-starter-web - \ No newline at end of file + diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/src/main/resources/bootstrap.yml index c6d40440..137da64e 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/src/main/resources/bootstrap.yml @@ -7,8 +7,6 @@ spring: cloud: tencent: metadata: - content: - env: blue polaris: address: grpc://183.47.111.80:8091 namespace: default 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 6454f43d..f96b25e1 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 @@ -18,6 +18,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 755b04d6..24efc40c 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 f5010026..7cfe79de 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,11 +6,15 @@ spring: name: GatewayScgService cloud: tencent: - metadata: - content: - env: blue - transitive: - - env + 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-plugin-starters/pom.xml b/spring-cloud-tencent-plugin-starters/pom.xml new file mode 100644 index 00000000..12731509 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/pom.xml @@ -0,0 +1,22 @@ + + + + spring-cloud-tencent + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-plugin-starters + pom + Spring Cloud Starter Tencent Solution + + + spring-cloud-tencent-featureenv-plugin + spring-cloud-tencent-gateway-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 00000000..25cce93d --- /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 00000000..d341ba34 --- /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 00000000..4aa0aa45 --- /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 00000000..eceb569e --- /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 = "env"; + 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 00000000..e67ca364 --- /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 00000000..b61fbdfa --- /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 00000000..b83f587c --- /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("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("env")); + } + + @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 00000000..06a9c65e --- /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 00000000..f0daf20a --- /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 00000000..226563bb --- /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 00000000..0eb40c3a --- /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 00000000..dda97563 --- /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,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.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 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 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(); + for (int i = size - 1; i >= 0; i--) { + TrafficStainer stainer = trafficStainers.get(i); + Map labels = stainer.apply(exchange); + if (!CollectionUtils.isEmpty(labels)) { + stainedLabels.putAll(labels); + } + } + 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 00000000..e58df27e --- /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 00000000..115e2ca4 --- /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 00000000..f96ec2a7 --- /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 00000000..d7614565 --- /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 00000000..2ecb0cce --- /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 00000000..927618f8 --- /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": true, + "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 00000000..70c95961 --- /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 00000000..6339691d --- /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 00000000..5bb4757d --- /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.EQUAL.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.EQUAL.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.EQUAL.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 00000000..1ac88e3c --- /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()); + } +}