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