add feature-env plugin & add spring cloud gateway staining plugin (#428)

pull/434/head
lepdou 2 years ago committed by GitHub
parent be0d3cb14f
commit 8f0971efcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,3 +3,4 @@
- [Bugfix: optimize ratelimit actuator](https://github.com/Tencent/spring-cloud-tencent/pull/413)
- [Feature: add rate limit filter debug log](https://github.com/Tencent/spring-cloud-tencent/pull/417)
- [Feature: add feature-env plugin & add spring cloud gateway staining plugin](https://github.com/Tencent/spring-cloud-tencent/pull/428)

@ -47,6 +47,7 @@
<module>spring-cloud-starter-tencent-polaris-ratelimit</module>
<module>spring-cloud-starter-tencent-polaris-circuitbreaker</module>
<module>spring-cloud-starter-tencent-polaris-router</module>
<module>spring-cloud-tencent-plugin-starters</module>
<module>spring-cloud-tencent-dependencies</module>
<module>spring-cloud-tencent-examples</module>
<module>spring-cloud-tencent-coverage</module>

@ -25,6 +25,7 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import com.tencent.cloud.common.constant.MetadataConstant;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpHeaders;
@ -38,9 +39,6 @@ import org.springframework.web.server.ServerWebExchange;
*/
public final class CustomTransitiveMetadataResolver {
private static final String TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-";
private static final int TRANSITIVE_HEADER_PREFIX_LENGTH = TRANSITIVE_HEADER_PREFIX.length();
private CustomTransitiveMetadataResolver() {
}
@ -50,10 +48,20 @@ public final class CustomTransitiveMetadataResolver {
HttpHeaders headers = exchange.getRequest().getHeaders();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String key = entry.getKey();
if (StringUtils.isNotBlank(key)
&& StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX)
if (StringUtils.isBlank(key)) {
continue;
}
// resolve sct transitive header
if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX)
&& !CollectionUtils.isEmpty(entry.getValue())) {
String sourceKey = StringUtils.substring(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, entry.getValue().get(0));
}
//resolve polaris transitive header
if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX)
&& !CollectionUtils.isEmpty(entry.getValue())) {
String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH);
String sourceKey = StringUtils.substring(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, entry.getValue().get(0));
}
}
@ -67,11 +75,21 @@ public final class CustomTransitiveMetadataResolver {
Enumeration<String> headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
String key = headers.nextElement();
if (StringUtils.isBlank(key)) {
continue;
}
// resolve sct transitive header
if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX)
&& StringUtils.isNotBlank(request.getHeader(key))) {
String sourceKey = StringUtils.substring(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, request.getHeader(key));
}
if (StringUtils.isNotBlank(key)
&& StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX)
// resolve polaris transitive header
if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX)
&& StringUtils.isNotBlank(request.getHeader(key))) {
String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH);
String sourceKey = StringUtils.substring(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, request.getHeader(key));
}
}

@ -59,11 +59,10 @@ public class EncodeTransferMedataScgFilter implements GlobalFilter, Ordered {
// get metadata of current thread
MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT);
// add new metadata and cover old
if (metadataContext == null) {
metadataContext = MetadataContextHolder.get();
}
Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
if (!CollectionUtils.isEmpty(customMetadata)) {
String metadataStr = JacksonUtils.serialize2Json(customMetadata);

@ -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<String, String> 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<String, String> 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<String, String> resolve = CustomTransitiveMetadataResolver.resolve(request);
Assertions.assertThat(resolve.size()).isEqualTo(1);
Assertions.assertThat(resolve.get("a")).isEqualTo("test");
}
}

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

@ -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<String, String> getRuleExpressionLabels(ServerWebExchange exchange, String namespace, String service) {
Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service);
return ExpressionLabelUtils.resolve(exchange, expressionLabels);
return SpringWebExpressionLabelUtils.resolve(exchange, expressionLabels);
}
}

@ -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<String, String> getRuleExpressionLabels(HttpServletRequest request, String namespace, String service) {
Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service);
return ExpressionLabelUtils.resolve(request, expressionLabels);
return ServletExpressionLabelUtils.resolve(request, expressionLabels);
}
}

@ -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<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic;
private static MockedStatic<SpringWebExpressionLabelUtils> 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);

@ -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<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic;
private static MockedStatic<SpringWebExpressionLabelUtils> expressionLabelUtilsMockedStatic;
private PolarisRateLimiterLabelServletResolver labelResolver =
exchange -> Collections.singletonMap("ServletResolver", "ServletResolver");
private QuotaCheckServletFilter quotaCheckServletFilter;
@ -82,8 +82,8 @@ public class QuotaCheckServletFilterTest {
@BeforeClass
public static void beforeClass() {
expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class);
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet()))
expressionLabelUtilsMockedStatic = mockStatic(SpringWebExpressionLabelUtils.class);
when(SpringWebExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet()))
.thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);

@ -19,10 +19,7 @@
package com.tencent.cloud.polaris.router;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
@ -40,15 +37,11 @@ import com.tencent.cloud.common.pojo.PolarisServer;
import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils;
import com.tencent.cloud.polaris.loadbalancer.PolarisWeightedRule;
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor;
import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInfo;
import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.plugins.router.metadata.MetadataRouter;
import com.tencent.polaris.plugins.router.nearby.NearbyRouter;
import com.tencent.polaris.plugins.router.rule.RuleBasedRouter;
import com.tencent.polaris.router.api.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse;
@ -82,24 +75,21 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule {
final static String STRATEGY_AVAILABILITY_FILTERING = "availabilityFilteringRule";
private final PolarisLoadBalancerProperties loadBalancerProperties;
private final PolarisNearByRouterProperties polarisNearByRouterProperties;
private final PolarisMetadataRouterProperties polarisMetadataRouterProperties;
private final PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties;
private final RouterAPI routerAPI;
private final List<RouterRequestInterceptor> requestInterceptors;
private final List<RouterResponseInterceptor> responseInterceptors;
private final AbstractLoadBalancerRule delegateRule;
public PolarisLoadBalancerCompositeRule(RouterAPI routerAPI,
PolarisLoadBalancerProperties polarisLoadBalancerProperties,
PolarisNearByRouterProperties polarisNearByRouterProperties,
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties,
IClientConfig iClientConfig) {
IClientConfig iClientConfig,
List<RouterRequestInterceptor> requestInterceptors,
List<RouterResponseInterceptor> responseInterceptors) {
this.routerAPI = routerAPI;
this.polarisNearByRouterProperties = polarisNearByRouterProperties;
this.loadBalancerProperties = polarisLoadBalancerProperties;
this.polarisMetadataRouterProperties = polarisMetadataRouterProperties;
this.polarisRuleBasedRouterProperties = polarisRuleBasedRouterProperties;
this.requestInterceptors = requestInterceptors;
this.responseInterceptors = responseInterceptors;
delegateRule = getRule();
delegateRule.initWithNiwsConfig(iClientConfig);
@ -118,80 +108,66 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule {
}
// 2. filter by router
List<Server> serversAfterRouter = doRouter(allServers, key);
// 3. filter by load balance.
// A LoadBalancer needs to be regenerated for each request,
// because the list of servers may be different after filtered by router
ILoadBalancer loadBalancer = new SimpleLoadBalancer();
loadBalancer.addServers(serversAfterRouter);
delegateRule.setLoadBalancer(loadBalancer);
if (key instanceof PolarisRouterContext) {
PolarisRouterContext routerContext = (PolarisRouterContext) key;
List<Server> serversAfterRouter = doRouter(allServers, routerContext);
// 3. filter by load balance.
// A LoadBalancer needs to be regenerated for each request,
// because the list of servers may be different after filtered by router
ILoadBalancer loadBalancer = new SimpleLoadBalancer();
loadBalancer.addServers(serversAfterRouter);
delegateRule.setLoadBalancer(loadBalancer);
}
return delegateRule.choose(key);
}
List<Server> doRouter(List<Server> allServers, Object key) {
List<Server> doRouter(List<Server> allServers, PolarisRouterContext routerContext) {
ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(allServers);
// filter instance by routers
ProcessRoutersRequest processRoutersRequest = buildProcessRoutersRequest(serviceInstances, key);
ProcessRoutersRequest processRoutersRequest = buildProcessRoutersBaseRequest(serviceInstances);
// process request interceptors
processRouterRequestInterceptors(processRoutersRequest, routerContext);
// process router chain
ProcessRoutersResponse processRoutersResponse = routerAPI.processRouters(processRoutersRequest);
List<Server> filteredInstances = new ArrayList<>();
// process response interceptors
processRouterResponseInterceptors(routerContext, processRoutersResponse);
// transfer polaris server to ribbon server
ServiceInstances filteredServiceInstances = processRoutersResponse.getServiceInstances();
List<Server> filteredInstances = new ArrayList<>();
for (Instance instance : filteredServiceInstances.getInstances()) {
filteredInstances.add(new PolarisServer(serviceInstances, instance));
}
return filteredInstances;
}
ProcessRoutersRequest buildProcessRoutersRequest(ServiceInstances serviceInstances, Object key) {
ProcessRoutersRequest buildProcessRoutersBaseRequest(ServiceInstances serviceInstances) {
ProcessRoutersRequest processRoutersRequest = new ProcessRoutersRequest();
processRoutersRequest.setDstInstances(serviceInstances);
// metadata router
if (polarisMetadataRouterProperties.isEnabled()) {
Map<String, String> transitiveLabels = getRouterLabels(key, PolarisRouterContext.TRANSITIVE_LABELS);
processRoutersRequest.putRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, transitiveLabels);
}
// nearby router
if (polarisNearByRouterProperties.isEnabled()) {
Map<String, String> 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<String, String> ruleRouterMetadata = new HashMap<>();
ruleRouterMetadata.put(RuleBasedRouter.ROUTER_ENABLED, String.valueOf(ruleBasedRouterEnabled));
processRoutersRequest.putRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED, ruleRouterMetadata);
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setNamespace(MetadataContext.LOCAL_NAMESPACE);
serviceInfo.setService(MetadataContext.LOCAL_SERVICE);
if (ruleBasedRouterEnabled) {
Map<String, String> ruleRouterLabels = getRouterLabels(key, PolarisRouterContext.RULE_ROUTER_LABELS);
// The label information that the rule based routing depends on
// is placed in the metadata of the source service for transmission.
// Later, can consider putting it in routerMetadata like other routers.
serviceInfo.setMetadata(ruleRouterLabels);
}
processRoutersRequest.setSourceService(serviceInfo);
return processRoutersRequest;
}
private Map<String, String> getRouterLabels(Object key, String type) {
if (key instanceof PolarisRouterContext) {
return ((PolarisRouterContext) key).getLabels(type);
void processRouterRequestInterceptors(ProcessRoutersRequest processRoutersRequest, PolarisRouterContext routerContext) {
for (RouterRequestInterceptor requestInterceptor : requestInterceptors) {
requestInterceptor.apply(processRoutersRequest, routerContext);
}
}
private void processRouterResponseInterceptors(PolarisRouterContext routerContext, ProcessRoutersResponse processRoutersResponse) {
if (!CollectionUtils.isEmpty(responseInterceptors)) {
for (RouterResponseInterceptor responseInterceptor : responseInterceptors) {
responseInterceptor.apply(processRoutersResponse, routerContext);
}
}
return Collections.emptyMap();
}
public AbstractLoadBalancerRule getRule() {

@ -18,9 +18,14 @@
package com.tencent.cloud.polaris.router;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
@ -30,15 +35,14 @@ import org.springframework.util.CollectionUtils;
* @author lepdou 2022-05-17
*/
public class PolarisRouterContext {
/**
* the label for rule router.
* the labels for rule router, contain transitive metadata.
*/
public static final String RULE_ROUTER_LABELS = "ruleRouter";
public static final String ROUTER_LABELS = "allMetadata";
/**
* transitive labels.
*/
public static final String TRANSITIVE_LABELS = "transitive";
public static final String TRANSITIVE_LABELS = "transitiveMetadata";
private Map<String, Map<String, String>> labels;
@ -53,7 +57,54 @@ public class PolarisRouterContext {
return Collections.unmodifiableMap(subLabels);
}
public void setLabels(String labelType, Map<String, String> subLabels) {
public Map<String, String> getLabels(String labelType, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> typeLabels = getLabels(labelType);
if (CollectionUtils.isEmpty(typeLabels)) {
return Collections.emptyMap();
}
Map<String, String> 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<String, String> routerLabels = labels.get(ROUTER_LABELS);
if (CollectionUtils.isEmpty(routerLabels)) {
return StringUtils.EMPTY;
}
return routerLabels.get(labelKey);
}
public Set<String> getLabelAsSet(String labelKey) {
Map<String, String> routerLabels = labels.get(ROUTER_LABELS);
if (CollectionUtils.isEmpty(routerLabels)) {
return Collections.emptySet();
}
for (Map.Entry<String, String> 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<String, String> subLabels) {
if (this.labels == null) {
this.labels = new HashMap<>();
}

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

@ -18,13 +18,14 @@
package com.tencent.cloud.polaris.router.config;
import java.util.List;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.IRule;
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties;
import com.tencent.cloud.polaris.router.PolarisLoadBalancerCompositeRule;
import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor;
import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor;
import com.tencent.polaris.router.api.core.RouterAPI;
import org.springframework.context.annotation.Bean;
@ -39,12 +40,9 @@ public class RibbonConfiguration {
@Bean
public IRule polarisLoadBalancerCompositeRule(RouterAPI routerAPI,
PolarisLoadBalancerProperties polarisLoadBalancerProperties,
PolarisNearByRouterProperties polarisNearByRouterProperties,
PolarisMetadataRouterProperties polarisMetadataRouterProperties,
PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties,
IClientConfig iClientConfig) {
return new PolarisLoadBalancerCompositeRule(routerAPI, polarisLoadBalancerProperties,
polarisNearByRouterProperties, polarisMetadataRouterProperties,
polarisRuleBasedRouterProperties, iClientConfig);
IClientConfig iClientConfig, List<RouterRequestInterceptor> requestInterceptors,
List<RouterResponseInterceptor> responseInterceptors) {
return new PolarisLoadBalancerCompositeRule(routerAPI, polarisLoadBalancerProperties, iClientConfig,
requestInterceptors, responseInterceptors);
}
}

@ -25,8 +25,12 @@ import com.tencent.cloud.polaris.router.beanprocessor.LoadBalancerInterceptorBea
import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.interceptor.MetadataRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.NearbyRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.RuleBasedRouterRequestInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -63,4 +67,22 @@ public class RouterAutoConfiguration {
public RouterRuleLabelResolver routerRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
return new RouterRuleLabelResolver(serviceRuleManager);
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.polaris.router.metadata-router.enabled", matchIfMissing = true)
public MetadataRouterRequestInterceptor metadataRouterRequestInterceptor(PolarisMetadataRouterProperties polarisMetadataRouterProperties) {
return new MetadataRouterRequestInterceptor(polarisMetadataRouterProperties);
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.polaris.router.nearby-router.enabled", matchIfMissing = true)
public NearbyRouterRequestInterceptor nearbyRouterRequestInterceptor(PolarisNearByRouterProperties polarisNearByRouterProperties) {
return new NearbyRouterRequestInterceptor(polarisNearByRouterProperties);
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.polaris.router.rule-router.enabled", matchIfMissing = true)
public RuleBasedRouterRequestInterceptor ruleBasedRouterRequestInterceptor(PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) {
return new RuleBasedRouterRequestInterceptor(polarisRuleBasedRouterProperties);
}
}

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

@ -71,7 +71,7 @@ public class PolarisFeignLoadBalancer extends FeignLoadBalancer {
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get()
routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE));
Map<String, String> labelHeaderValuesMap = new HashMap<>();
@ -86,7 +86,7 @@ public class PolarisFeignLoadBalancer extends FeignLoadBalancer {
catch (UnsupportedEncodingException e) {
throw new RuntimeException("unsupported charset exception " + UTF_8);
}
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labelHeaderValuesMap);
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labelHeaderValuesMap);
return routerContext;
}

@ -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<String> metadataRouterKeys = routerContext.getLabelAsSet(LABEL_KEY_METADATA_ROUTER_KEYS);
// 2. get metadata router labels
Map<String, String> metadataRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS,
metadataRouterKeys);
// 3. set metadata router labels to request
request.addRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, metadataRouterLabels);
}
}

@ -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<String, String> nearbyRouterMetadata = new HashMap<>();
nearbyRouterMetadata.put(NearbyRouter.ROUTER_ENABLED, "true");
request.addRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY, nearbyRouterMetadata);
}
}

@ -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<String, String> 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<String, String> ruleRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS);
request.getSourceService().setMetadata(ruleRouterLabels);
}
}
}

@ -30,7 +30,7 @@ import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
@ -137,8 +137,8 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels);
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
return routerContext;
}
@ -151,6 +151,6 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
return Collections.emptyMap();
}
return ExpressionLabelUtils.resolve(request, labelKeys);
return SpringWebExpressionLabelUtils.resolve(request, labelKeys);
}
}

@ -29,7 +29,7 @@ import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
@ -126,8 +126,8 @@ public class PolarisLoadBalancerClientFilter extends LoadBalancerClientFilter {
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels);
routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels);
return routerContext;
}
@ -140,6 +140,6 @@ public class PolarisLoadBalancerClientFilter extends LoadBalancerClientFilter {
return Collections.emptyMap();
}
return ExpressionLabelUtils.resolve(exchange, labelKeys);
return SpringWebExpressionLabelUtils.resolve(exchange, labelKeys);
}
}

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

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

@ -18,6 +18,7 @@
package com.tencent.cloud.polaris.router;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@ -44,6 +45,10 @@ import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperti
import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.interceptor.MetadataRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.NearbyRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.RuleBasedRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.DefaultServiceInstances;
import com.tencent.polaris.api.pojo.Instance;
@ -87,23 +92,28 @@ public class PolarisLoadBalancerCompositeRuleTest {
private PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties;
@Mock
private RouterAPI routerAPI;
private IClientConfig config;
private String testNamespace = "testNamespace";
private String testCallerService = "testCallerService";
private String testCalleeService = "testCalleeService";
private final List<RouterRequestInterceptor> requestInterceptors = new ArrayList<>();
@Before
public void before() {
config = new DefaultClientConfigImpl();
config.loadDefaultValues();
requestInterceptors.add(new MetadataRouterRequestInterceptor(polarisMetadataRouterProperties));
requestInterceptors.add(new NearbyRouterRequestInterceptor(polarisNearByRouterProperties));
requestInterceptors.add(new RuleBasedRouterRequestInterceptor(polarisRuleBasedRouterProperties));
}
@Test
public void testGetDefaultLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn("");
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule defaultRule = compositeRule.getRule();
@ -114,8 +124,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void testRandomLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RANDOM);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -126,8 +135,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void testWeightLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_WEIGHT);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -138,8 +146,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void testRetryLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RETRY);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -150,8 +157,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void testWeightedResponseTimeLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RESPONSE_TIME_WEIGHTED);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -162,8 +168,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void tesBestAvailableLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_BEST_AVAILABLE);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -174,8 +179,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void tesRoundRobinLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_ROUND_ROBIN);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -186,8 +190,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
public void testAvailabilityFilteringLB() {
when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_AVAILABILITY_FILTERING);
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
AbstractLoadBalancerRule lbRule = compositeRule.getRule();
@ -206,13 +209,18 @@ public class PolarisLoadBalancerCompositeRuleTest {
setTransitiveMetadata();
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext);
Map<String, String> oldRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS);
Map<String, String> newRouterLabels = new HashMap<>(oldRouterLabels);
newRouterLabels.put("system-metadata-router-keys", "k2");
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, newRouterLabels);
ProcessRoutersRequest request = compositeRule.buildProcessRoutersBaseRequest(serviceInstances);
compositeRule.processRouterRequestInterceptors(request, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA);
@ -236,13 +244,13 @@ public class PolarisLoadBalancerCompositeRuleTest {
setTransitiveMetadata();
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext);
ProcessRoutersRequest request = compositeRule.buildProcessRoutersBaseRequest(serviceInstances);
compositeRule.processRouterRequestInterceptors(request, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY);
@ -267,13 +275,13 @@ public class PolarisLoadBalancerCompositeRuleTest {
setTransitiveMetadata();
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
ServiceInstances serviceInstances = assembleServiceInstances();
PolarisRouterContext routerContext = assembleRouterContext();
ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext);
ProcessRoutersRequest request = compositeRule.buildProcessRoutersBaseRequest(serviceInstances);
compositeRule.processRouterRequestInterceptors(request, routerContext);
Map<String, String> routerMetadata = request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED);
@ -298,8 +306,7 @@ public class PolarisLoadBalancerCompositeRuleTest {
setTransitiveMetadata();
PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI,
polarisLoadBalancerProperties, polarisNearByRouterProperties,
polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config);
polarisLoadBalancerProperties, config, requestInterceptors, null);
ProcessRoutersResponse assembleResponse = assembleProcessRoutersResponse();
when(routerAPI.processRouters(any())).thenReturn(assembleResponse);
@ -339,8 +346,8 @@ public class PolarisLoadBalancerCompositeRuleTest {
Map<String, String> 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;
}

@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> labels = new HashMap<>();
labels.put("k1", "v1,v2,v3");
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
Set<String> resolvedLabels = routerContext.getLabelAsSet("k1");
Assert.assertEquals(3, resolvedLabels.size());
Assert.assertTrue(resolvedLabels.contains("v1"));
Assert.assertTrue(resolvedLabels.contains("v2"));
Assert.assertTrue(resolvedLabels.contains("v3"));
}
}

@ -85,7 +85,7 @@ public class PolarisFeignLoadBalancerTest {
PolarisRouterContext routerContext = polarisFeignLoadBalancer.buildRouterContext(headers);
Assert.assertNotNull(routerContext);
Map<String, String> routerLabels = routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS);
Map<String, String> routerLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS);
Assert.assertNotNull(routerLabels);
Assert.assertEquals("v1", routerLabels.get("k1"));
Assert.assertEquals("v2", routerLabels.get("k2"));

@ -210,11 +210,11 @@ public class PolarisLoadBalancerInterceptorTest {
Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).get("k1"));
Assert.assertEquals("v22", routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).get("k2"));
Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k1"));
Assert.assertEquals("v22", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k2"));
Assert.assertEquals("v4", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k4"));
Assert.assertEquals("GET", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("${http.method}"));
Assert.assertEquals("/user/get", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("${http.uri}"));
Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k1"));
Assert.assertEquals("v22", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k2"));
Assert.assertEquals("v4", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k4"));
Assert.assertEquals("GET", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("${http.method}"));
Assert.assertEquals("/user/get", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("${http.uri}"));
}
}
}

@ -128,7 +128,7 @@ public class PolarisLoadBalancerClientFilterTest {
PolarisRouterContext routerContext = polarisLoadBalancerClientFilter.genRouterContext(webExchange, calleeService);
Map<String, String> routerLabels = routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS);
Map<String, String> routerLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS);
Assert.assertEquals("v1", routerLabels.get("${http.header.k1}"));
Assert.assertEquals("zhangsan", routerLabels.get("${http.query.userid}"));
Assert.assertEquals("blue", routerLabels.get("env"));

@ -26,6 +26,27 @@ import org.springframework.core.Ordered;
*/
public final class MetadataConstant {
/**
* sct transitive header prefix.
*/
public static final String SCT_TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-";
/**
* sct transitive header prefix length.
*/
public static final int SCT_TRANSITIVE_HEADER_PREFIX_LENGTH = SCT_TRANSITIVE_HEADER_PREFIX.length();
/**
* polaris transitive header prefix.
*/
public static final String POLARIS_TRANSITIVE_HEADER_PREFIX = "X-Polaris-Metadata-Transitive-";
/**
* polaris transitive header prefix length.
*/
public static final int POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH = POLARIS_TRANSITIVE_HEADER_PREFIX.length();
private MetadataConstant() {
}
/**
* Order of filter, interceptor, ...
*/

@ -19,11 +19,8 @@
package com.tencent.cloud.common.metadata.config;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter;
import com.tencent.cloud.common.spi.InstanceMetadataProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
@ -51,16 +48,4 @@ public class MetadataAutoConfiguration {
return new StaticMetadataManager(metadataLocalProperties, instanceMetadataProvider);
}
/**
* Create when gateway application is SCG.
*/
@Configuration
@ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.GlobalFilter")
static class MetadataScgFilterConfig {
@Bean
public GlobalFilter metadataFirstScgFilter() {
return new MetadataFirstScgFilter();
}
}
}

@ -1,62 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.common.metadata.filter.gateway;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER;
/**
* Scg output first filter used for setting peer info in context.
*
* @author Haotian Zhang
*/
public class MetadataFirstScgFilter implements GlobalFilter, Ordered {
/**
* Order of MetadataFirstScgFilter.
*/
public static final int METADATA_FIRST_FILTER_ORDER = ROUTE_TO_URL_FILTER_ORDER + 1;
@Override
public int getOrder() {
return METADATA_FIRST_FILTER_ORDER;
}
@Override
public Mono<Void> 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);
}
}

@ -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<String> values;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public List<String> getValues() {
return values;
}
public void setValues(List<String> 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 + '\'' +
'}';
}
}

@ -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<String, String> actualValues, List<Condition> conditions) {
boolean allMatched = true;
for (Condition condition : conditions) {
List<String> 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;
}
}

@ -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 + '\'' +
'}';
}
}

@ -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<String, String> toMap(List<KVPair> labels) {
if (CollectionUtils.isEmpty(labels)) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>();
labels.forEach(label -> {
result.put(label.getKey(), label.getValue());
});
return result;
}
}

@ -0,0 +1,133 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.common.rule;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
/**
* The condition operation.
* @author lepdou 2022-07-11
*/
public enum Operation {
/**
* case sensitive string equals.
*/
EQUAL("EQUAL"),
/**
* case sensitive string not equals.
*/
NOT_EQUAL("NOT_EQUAL"),
/**
* whether element in collection.
*/
IN("IN"),
/**
* whether element not in collection.
*/
NOT_IN("NOT_IN"),
/**
* regex operation.
*/
REGEX("REGEX"),
/**
* whether element is blank.
*/
BLANK("BLANK"),
/**
* whether element is not blank.
*/
NOT_BLANK("NOT_BLANK");
private final String value;
Operation(String value) {
this.value = value;
}
public static boolean match(List<String> expectedValues, String actualValue, String rawOperation) {
String firstExpectedValue = null;
if (!CollectionUtils.isEmpty(expectedValues)) {
firstExpectedValue = expectedValues.get(0);
}
switch (getOperation(rawOperation)) {
case EQUAL:
return firstExpectedValue != null && StringUtils.equals(actualValue, firstExpectedValue);
case NOT_EQUAL:
return firstExpectedValue == null || !StringUtils.equals(actualValue, firstExpectedValue);
case BLANK:
return StringUtils.isBlank(actualValue);
case NOT_BLANK:
return !StringUtils.isBlank(actualValue);
case IN:
if (CollectionUtils.isEmpty(expectedValues)) {
return false;
}
return expectedValues.contains(actualValue);
case NOT_IN:
if (CollectionUtils.isEmpty(expectedValues)) {
return true;
}
return !expectedValues.contains(actualValue);
case REGEX:
if (firstExpectedValue == null) {
return false;
}
Pattern r = Pattern.compile(firstExpectedValue);
return r.matcher(actualValue).matches();
default:
return false;
}
}
public static Operation getOperation(String operation) {
if (StringUtils.equalsIgnoreCase(operation, EQUAL.value)) {
return EQUAL;
}
if (StringUtils.equalsIgnoreCase(operation, NOT_EQUAL.value)) {
return NOT_EQUAL;
}
if (StringUtils.equalsIgnoreCase(operation, IN.value)) {
return IN;
}
if (StringUtils.equalsIgnoreCase(operation, NOT_IN.value)) {
return NOT_IN;
}
if (StringUtils.equalsIgnoreCase(operation, REGEX.value)) {
return REGEX;
}
if (StringUtils.equalsIgnoreCase(operation, BLANK.value)) {
return BLANK;
}
if (StringUtils.equalsIgnoreCase(operation, NOT_BLANK.value)) {
return NOT_BLANK;
}
throw new RuntimeException("Unsupported operation. operation = " + operation);
}
public String getValue() {
return value;
}
}

@ -1,319 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.common.util;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
/**
* the utils for parse label expression.
*
* @author lepdou 2022-05-13
* @author cheese8 2022-06-20
*/
public final class ExpressionLabelUtils {
/**
* the expression prefix of header label.
*/
public static final String LABEL_HEADER_PREFIX = "${http.header.";
/**
* the length of expression header label prefix.
*/
public static final int LABEL_HEADER_PREFIX_LEN = LABEL_HEADER_PREFIX.length();
/**
* the expression prefix of query.
*/
public static final String LABEL_QUERY_PREFIX = "${http.query.";
/**
* the length of expression query label prefix.
*/
public static final int LABEL_QUERY_PREFIX_LEN = LABEL_QUERY_PREFIX.length();
/**
* the expression prefix of cookie.
*/
public static final String LABEL_COOKIE_PREFIX = "${http.cookie.";
/**
* the length of expression cookie label prefix.
*/
public static final int LABEL_COOKIE_PREFIX_LEN = LABEL_COOKIE_PREFIX.length();
/**
* the expression of method.
*/
public static final String LABEL_METHOD = "${http.method}";
/**
* the expression of uri.
*/
public static final String LABEL_URI = "${http.uri}";
/**
* the suffix of expression.
*/
public static final String LABEL_SUFFIX = "}";
private ExpressionLabelUtils() {
}
public static boolean isExpressionLabel(String labelKey) {
if (StringUtils.isEmpty(labelKey)) {
return false;
}
if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey) ||
StringUtils.startsWithIgnoreCase(LABEL_URI, labelKey)) {
return true;
}
return (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX) ||
StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX) ||
StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX))
&& StringUtils.endsWith(labelKey, LABEL_SUFFIX);
}
public static Map<String, String> resolve(HttpServletRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> 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<String, String> resolve(ServerWebExchange exchange, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> 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<String, String> resolve(HttpRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> 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<String, String> 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<String, Collection<String>> valueMaps, String key) {
if (CollectionUtils.isEmpty(valueMaps)) {
return StringUtils.EMPTY;
}
Collection<String> values = valueMaps.get(key);
if (CollectionUtils.isEmpty(values)) {
return StringUtils.EMPTY;
}
for (String value : values) {
return value;
}
return StringUtils.EMPTY;
}
}

@ -61,6 +61,16 @@ public final class JacksonUtils {
}
}
public static <T> T deserialize(String jsonStr, Class<T> 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

@ -0,0 +1,136 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.common.util.expresstion;
import java.util.Collection;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
/**
* the utils for parse label expression.
*
* @author lepdou 2022-05-13
* @author cheese8 2022-06-20
*/
public final class ExpressionLabelUtils {
/**
* the expression prefix of header label.
*/
public static final String LABEL_HEADER_PREFIX = "${http.header.";
/**
* the length of expression header label prefix.
*/
public static final int LABEL_HEADER_PREFIX_LEN = LABEL_HEADER_PREFIX.length();
/**
* the expression prefix of query.
*/
public static final String LABEL_QUERY_PREFIX = "${http.query.";
/**
* the length of expression query label prefix.
*/
public static final int LABEL_QUERY_PREFIX_LEN = LABEL_QUERY_PREFIX.length();
/**
* the expression prefix of cookie.
*/
public static final String LABEL_COOKIE_PREFIX = "${http.cookie.";
/**
* the length of expression cookie label prefix.
*/
public static final int LABEL_COOKIE_PREFIX_LEN = LABEL_COOKIE_PREFIX.length();
/**
* the expression of method.
*/
public static final String LABEL_METHOD = "${http.method}";
/**
* the expression of uri.
*/
public static final String LABEL_URI = "${http.uri}";
/**
* the suffix of expression.
*/
public static final String LABEL_SUFFIX = "}";
private ExpressionLabelUtils() {
}
public static boolean isExpressionLabel(String labelKey) {
if (StringUtils.isEmpty(labelKey)) {
return false;
}
if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey) ||
StringUtils.startsWithIgnoreCase(LABEL_URI, labelKey)) {
return true;
}
return (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX) ||
StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX) ||
StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX))
&& StringUtils.endsWith(labelKey, LABEL_SUFFIX);
}
public static String parseHeaderKey(String expression) {
return expression.substring(LABEL_HEADER_PREFIX_LEN, expression.length() - 1);
}
public static String parseQueryKey(String expression) {
return expression.substring(LABEL_QUERY_PREFIX_LEN, expression.length() - 1);
}
public static String parseCookieKey(String expression) {
return expression.substring(LABEL_COOKIE_PREFIX_LEN, expression.length() - 1);
}
public static String getQueryValue(String queryString, String queryKey) {
if (StringUtils.isBlank(queryString)) {
return StringUtils.EMPTY;
}
String[] queries = StringUtils.split(queryString, "&");
if (queries == null || queries.length == 0) {
return StringUtils.EMPTY;
}
for (String query : queries) {
String[] queryKV = StringUtils.split(query, "=");
if (queryKV != null && queryKV.length == 2 && StringUtils.equals(queryKV[0], queryKey)) {
return queryKV[1];
}
}
return StringUtils.EMPTY;
}
public static String getFirstValue(Map<String, Collection<String>> valueMaps, String key) {
if (CollectionUtils.isEmpty(valueMaps)) {
return StringUtils.EMPTY;
}
Collection<String> values = valueMaps.get(key);
if (CollectionUtils.isEmpty(values)) {
return StringUtils.EMPTY;
}
for (String value : values) {
return value;
}
return StringUtils.EMPTY;
}
}

@ -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<String, String> resolve(HttpServletRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> 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;
}
}

@ -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<String, String> resolve(ServerWebExchange exchange, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> 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<String, String> resolve(HttpRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> 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<String, String> 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);
}
}

@ -18,7 +18,6 @@
package com.tencent.cloud.common.metadata.config;
import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter;
import org.assertj.core.api.Assertions;
import org.junit.Test;
@ -50,9 +49,6 @@ public class MetadataAutoConfigurationTest {
.withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class))
.run(context -> {
Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataScgFilterConfig.class);
Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class);
});
}
@ -65,9 +61,6 @@ public class MetadataAutoConfigurationTest {
.withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class))
.run(context -> {
Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataScgFilterConfig.class);
Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class);
});
}
@ -80,9 +73,6 @@ public class MetadataAutoConfigurationTest {
.withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class))
.run(context -> {
Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataScgFilterConfig.class);
Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class);
});
}
}

@ -0,0 +1,97 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.common.rule;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
/**
* Test for {@link Operation}.
* @author lepdou 2022-07-12
*/
@RunWith(MockitoJUnitRunner.class)
public class OperationTest {
@Test
public void testEqual() {
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "v1", Operation.EQUAL.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "v2", Operation.EQUAL.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList(""), "v2", Operation.EQUAL.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "", Operation.EQUAL.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), null, Operation.EQUAL.getValue()));
Assert.assertFalse(Operation.match(Collections.emptyList(), "v1", Operation.EQUAL.getValue()));
}
@Test
public void testNotEqual() {
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "v1", Operation.NOT_EQUAL.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "v2", Operation.NOT_EQUAL.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList(""), "v2", Operation.NOT_EQUAL.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "", Operation.NOT_EQUAL.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), null, Operation.NOT_EQUAL.getValue()));
Assert.assertTrue(Operation.match(Collections.emptyList(), "v1", Operation.NOT_EQUAL.getValue()));
}
@Test
public void testIn() {
Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "v1", Operation.IN.getValue()));
Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "v2", Operation.IN.getValue()));
Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "v4", Operation.IN.getValue()));
Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "", Operation.IN.getValue()));
Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), null, Operation.IN.getValue()));
Assert.assertFalse(Operation.match(Collections.emptyList(), null, Operation.IN.getValue()));
}
@Test
public void testNotIn() {
Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "v1", Operation.NOT_IN.getValue()));
Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "v2", Operation.NOT_IN.getValue()));
Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "v4", Operation.NOT_IN.getValue()));
Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "", Operation.NOT_IN.getValue()));
Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), null, Operation.NOT_IN.getValue()));
Assert.assertTrue(Operation.match(Collections.emptyList(), null, Operation.NOT_IN.getValue()));
}
@Test
public void testEmpty() {
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), null, Operation.BLANK.getValue()));
Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "", Operation.BLANK.getValue()));
Assert.assertTrue(Operation.match(Collections.emptyList(), null, Operation.BLANK.getValue()));
}
@Test
public void testNotEmpty() {
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), null, Operation.NOT_BLANK.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "", Operation.NOT_BLANK.getValue()));
Assert.assertFalse(Operation.match(Collections.emptyList(), null, Operation.NOT_BLANK.getValue()));
Assert.assertTrue(Operation.match(Collections.emptyList(), "v1", Operation.NOT_BLANK.getValue()));
}
@Test
public void testRegex() {
Assert.assertTrue(Operation.match(Collections.singletonList("v[1~10]"), "v1", Operation.REGEX.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v[1~10]"), "v12", Operation.REGEX.getValue()));
Assert.assertFalse(Operation.match(Collections.singletonList("v[1~10]*"), "v12", Operation.REGEX.getValue()));
}
}

@ -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<String, String> result = ExpressionLabelUtils.resolve(request, labelKeys);
Map<String, String> 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<String, String> result = ExpressionLabelUtils.resolve(exchange, labelKeys);
Map<String, String> 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<String, String> result = ExpressionLabelUtils.resolve(request, labelKeys);
Map<String, String> result = SpringWebExpressionLabelUtils.resolve(request, labelKeys);
Assert.assertEquals("zhangsan", result.get(validLabel1));
Assert.assertEquals("zhangsan", result.get(validLabel2));

@ -63,6 +63,16 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-config</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-featureenv-plugin</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-gateway-plugin</artifactId>
</dependency>
</dependencies>
<build>

@ -71,7 +71,7 @@
<properties>
<revision>1.7.0-Hoxton.SR12-SNAPSHOT</revision>
<polaris.version>1.7.0</polaris.version>
<polaris.version>1.7.1-SNAPSHOT</polaris.version>
<logback.version>1.2.11</logback.version>
<mocktio.version>4.5.1</mocktio.version>
<byte-buddy.version>1.12.10</byte-buddy.version>
@ -151,6 +151,19 @@
<version>${revision}</version>
</dependency>
<!-- spring cloud tencent plugins -->
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-featureenv-plugin</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-gateway-plugin</artifactId>
<version>${revision}</version>
</dependency>
<!-- third part framework dependencies -->
<dependency>
<groupId>com.google.guava</groupId>

@ -19,9 +19,14 @@
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
</project>

@ -7,8 +7,6 @@ spring:
cloud:
tencent:
metadata:
content:
env: blue
polaris:
address: grpc://183.47.111.80:8091
namespace: default

@ -18,6 +18,11 @@
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

@ -29,6 +29,16 @@
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-gateway-plugin</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-featureenv-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>

@ -6,11 +6,15 @@ spring:
name: GatewayScgService
cloud:
tencent:
metadata:
content:
env: blue
transitive:
- env
plugin:
scg:
staining:
enabled: true
rule-staining:
enabled: true
router:
feature-env:
enabled: true
polaris:
address: grpc://183.47.111.80:8091
namespace: default

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
<packaging>pom</packaging>
<name>Spring Cloud Starter Tencent Solution</name>
<modules>
<module>spring-cloud-tencent-featureenv-plugin</module>
<module>spring-cloud-tencent-gateway-plugin</module>
</modules>
</project>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-tencent-featureenv-plugin</artifactId>
<name>Spring Cloud Tencent Feature Environment Plugin</name>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

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

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

@ -0,0 +1,65 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.plugin.featureenv;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor;
import com.tencent.polaris.api.rpc.MetadataFailoverType;
import com.tencent.polaris.plugins.router.metadata.MetadataRouter;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import org.apache.commons.lang.StringUtils;
/**
* Build metadata router context for feature env scene.
* @author lepdou 2022-07-06
*/
public class FeatureEnvRouterRequestInterceptor implements RouterRequestInterceptor {
private static final String LABEL_KEY_FEATURE_ENV_ROUTER_KEY = "system-feature-env-router-label";
private static final String DEFAULT_FEATURE_ENV_ROUTER_LABEL = "env";
private static final String NOT_EXISTED_ENV = "NOT_EXISTED_ENV";
@Override
public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) {
//1. get feature env router label key
String envLabelKey = routerContext.getLabel(LABEL_KEY_FEATURE_ENV_ROUTER_KEY);
if (StringUtils.isBlank(envLabelKey)) {
envLabelKey = DEFAULT_FEATURE_ENV_ROUTER_LABEL;
}
//2. get feature env router label value
String envLabelValue = routerContext.getLabel(envLabelKey);
if (envLabelValue == null) {
// router to base env when not matched feature env
envLabelValue = NOT_EXISTED_ENV;
}
//3. set env metadata to router request
Map<String, String> envMetadata = new HashMap<>();
envMetadata.put(envLabelKey, envLabelValue);
request.addRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, envMetadata);
//4. set failover type to others
request.setMetadataFailoverType(MetadataFailoverType.METADATAFAILOVERNOTKEY);
}
}

@ -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."
}
]
}

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.plugin.featureenv.FeatureEnvAutoConfiguration

@ -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<String, String> labels = new HashMap<>();
labels.put("env", "blue");
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels);
ProcessRoutersRequest request = new ProcessRoutersRequest();
ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>());
request.setDstInstances(serviceInstances);
FeatureEnvRouterRequestInterceptor interceptor = new FeatureEnvRouterRequestInterceptor();
interceptor.apply(request, routerContext);
Map<String, String> metadataRouterLabels = request.getRouterMetadata().get(MetadataRouter.ROUTER_TYPE_METADATA);
Assert.assertEquals(1, metadataRouterLabels.size());
Assert.assertEquals("blue", metadataRouterLabels.get("env"));
}
@Test
public void testSpecifyRouterKey() {
Map<String, String> 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<String, String> 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<String, String> 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<String, String> metadataRouterLabels = request.getRouterMetadata().get(MetadataRouter.ROUTER_TYPE_METADATA);
Assert.assertEquals(1, metadataRouterLabels.size());
Assert.assertEquals("NOT_EXISTED_ENV", metadataRouterLabels.get("specify-env"));
}
}

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-tencent-gateway-plugin</artifactId>
<name>Spring Cloud Tencent Gateway Plugin</name>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

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

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

@ -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<String, String> apply(ServerWebExchange exchange);
}

@ -0,0 +1,104 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.plugin.gateway.staining;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER;
/**
* Staining the request, and the stained labels will be passed to the link through transitive metadata.
* @author lepdou 2022-07-06
*/
public class TrafficStainingGatewayFilter implements GlobalFilter, Ordered {
private final List<TrafficStainer> trafficStainers;
public TrafficStainingGatewayFilter(List<TrafficStainer> trafficStainers) {
if (!CollectionUtils.isEmpty(trafficStainers)) {
trafficStainers.sort(Comparator.comparingInt(Ordered::getOrder));
}
this.trafficStainers = trafficStainers;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (CollectionUtils.isEmpty(trafficStainers)) {
return chain.filter(exchange);
}
// 1. get stained labels from request
Map<String, String> 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<String, String> oldTransitiveMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
// append new transitive metadata
Map<String, String> newTransitiveMetadata = new HashMap<>(oldTransitiveMetadata);
newTransitiveMetadata.putAll(stainedLabels);
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE, newTransitiveMetadata);
}).build();
return chain.filter(exchange.mutate().request(request).build());
}
Map<String, String> getStainedLabels(ServerWebExchange exchange) {
Map<String, String> stainedLabels = new HashMap<>();
int size = trafficStainers.size();
for (int i = size - 1; i >= 0; i--) {
TrafficStainer stainer = trafficStainers.get(i);
Map<String, String> labels = stainer.apply(exchange);
if (!CollectionUtils.isEmpty(labels)) {
stainedLabels.putAll(labels);
}
}
return stainedLabels;
}
@Override
public int getOrder() {
return ROUTE_TO_URL_FILTER_ORDER + 1;
}
}

@ -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<String, String> execute(ServerWebExchange exchange, StainingRule stainingRule) {
if (stainingRule == null) {
return Collections.emptyMap();
}
List<StainingRule.Rule> rules = stainingRule.getRules();
if (CollectionUtils.isEmpty(rules)) {
return Collections.emptyMap();
}
Map<String, String> parsedLabels = new HashMap<>();
for (StainingRule.Rule rule : rules) {
List<Condition> conditions = rule.getConditions();
Set<String> keys = new HashSet<>();
conditions.forEach(condition -> keys.add(condition.getKey()));
Map<String, String> actualValues = SpringWebExpressionLabelUtils.resolve(exchange, keys);
if (!ConditionUtils.match(actualValues, conditions)) {
continue;
}
parsedLabels.putAll(KVPairUtils.toMap(rule.getLabels()));
}
return parsedLabels;
}
}

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

@ -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<String, String> apply(ServerWebExchange exchange) {
StainingRule stainingRule = stainingRuleManager.getStainingRule();
if (stainingRule == null) {
return Collections.emptyMap();
}
return ruleStainingExecutor.execute(exchange, stainingRule);
}
@Override
public int getOrder() {
return 0;
}
}

@ -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<Rule> rules;
public List<Rule> getRules() {
return rules;
}
public void setRules(List<Rule> rules) {
this.rules = rules;
}
@Override
public String toString() {
return "StainingRule{" +
"rules=" + rules +
'}';
}
public static class Rule {
private List<Condition> conditions;
private List<KVPair> labels;
public List<Condition> getConditions() {
return conditions;
}
public void setConditions(List<Condition> conditions) {
this.conditions = conditions;
}
public List<KVPair> getLabels() {
return labels;
}
public void setLabels(List<KVPair> labels) {
this.labels = labels;
}
@Override
public String toString() {
return "Rule{" +
"conditions=" + conditions +
", labels=" + labels +
'}';
}
}
}

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

@ -0,0 +1,40 @@
{
"properties": [
{
"name": "spring.cloud.tencent.plugin.scg.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for spring cloud gateway plugin."
},
{
"name": "spring.cloud.tencent.plugin.scg.staining.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for spring cloud gateway staining plugin."
},
{
"name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "the switch for spring cloud gateway rule staining plugin."
},
{
"name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.namespace",
"type": "java.lang.String",
"defaultValue": "default",
"description": "The namespace used to config staining rules."
},
{
"name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.group",
"type": "java.lang.String",
"defaultValue": "${spring.application.name}",
"description": "The group used to config staining rules."
},
{
"name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.fileName",
"type": "java.lang.String",
"defaultValue": "rule/staining.json",
"description": "The file name used to config staining rules."
}
]
}

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.plugin.gateway.SCGPluginsAutoConfiguration

@ -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<String, String> labels1 = new HashMap<>();
labels1.put("k1", "v1");
labels1.put("k2", "v2");
when(trafficStainer1.apply(exchange)).thenReturn(labels1);
Map<String, String> 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<String, String> 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"));
}
}

@ -0,0 +1,181 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.plugin.gateway.staining.rule;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import com.tencent.cloud.common.rule.Condition;
import com.tencent.cloud.common.rule.KVPair;
import com.tencent.cloud.common.rule.Operation;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
/**
* Test for {@link RuleStainingExecutor}.
* @author lepdou 2022-07-12
*/
@RunWith(MockitoJUnitRunner.class)
public class RuleStainingExecutorTest {
@Test
public void testMatchCondition() {
Condition condition1 = new Condition();
condition1.setKey("${http.header.uid}");
condition1.setOperation(Operation.EQUAL.toString());
condition1.setValues(Collections.singletonList("1000"));
Condition condition2 = new Condition();
condition2.setKey("${http.query.source}");
condition2.setOperation(Operation.IN.toString());
condition2.setValues(Collections.singletonList("wx"));
StainingRule.Rule rule = new StainingRule.Rule();
rule.setConditions(Arrays.asList(condition1, condition2));
KVPair kvPair = new KVPair();
kvPair.setKey("env");
kvPair.setValue("blue");
rule.setLabels(Collections.singletonList(kvPair));
StainingRule stainingRule = new StainingRule();
stainingRule.setRules(Collections.singletonList(rule));
MockServerHttpRequest request = MockServerHttpRequest.get("/users")
.queryParam("source", "wx")
.header("uid", "1000").build();
MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build();
RuleStainingExecutor executor = new RuleStainingExecutor();
Map<String, String> stainedLabels = executor.execute(exchange, stainingRule);
Assert.assertNotNull(stainedLabels);
Assert.assertEquals(1, stainedLabels.size());
Assert.assertEquals("blue", stainedLabels.get("env"));
}
@Test
public void testNotMatchCondition() {
Condition condition1 = new Condition();
condition1.setKey("${http.header.uid}");
condition1.setOperation(Operation.EQUAL.toString());
condition1.setValues(Collections.singletonList("1000"));
Condition condition2 = new Condition();
condition2.setKey("${http.query.source}");
condition2.setOperation(Operation.IN.toString());
condition2.setValues(Collections.singletonList("wx"));
StainingRule.Rule rule = new StainingRule.Rule();
rule.setConditions(Arrays.asList(condition1, condition2));
KVPair kvPair = new KVPair();
kvPair.setKey("env");
kvPair.setValue("blue");
rule.setLabels(Collections.singletonList(kvPair));
StainingRule stainingRule = new StainingRule();
stainingRule.setRules(Collections.singletonList(rule));
MockServerHttpRequest request = MockServerHttpRequest.get("/users")
.queryParam("source", "wx")
.header("uid", "10001").build();
MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build();
RuleStainingExecutor executor = new RuleStainingExecutor();
Map<String, String> stainedLabels = executor.execute(exchange, stainingRule);
Assert.assertNotNull(stainedLabels);
Assert.assertEquals(0, stainedLabels.size());
}
@Test
public void testMatchTwoRulesAndNotMatchOneRule() {
Condition condition1 = new Condition();
condition1.setKey("${http.header.uid}");
condition1.setOperation(Operation.EQUAL.toString());
condition1.setValues(Collections.singletonList("1000"));
Condition condition2 = new Condition();
condition2.setKey("${http.query.source}");
condition2.setOperation(Operation.IN.toString());
condition2.setValues(Collections.singletonList("wx"));
// rule1 matched
StainingRule.Rule rule1 = new StainingRule.Rule();
rule1.setConditions(Arrays.asList(condition1, condition2));
KVPair kvPair = new KVPair();
kvPair.setKey("env");
kvPair.setValue("blue");
rule1.setLabels(Collections.singletonList(kvPair));
// rule2 matched
StainingRule.Rule rule2 = new StainingRule.Rule();
rule2.setConditions(Collections.singletonList(condition1));
KVPair kvPair2 = new KVPair();
kvPair2.setKey("label1");
kvPair2.setValue("value1");
KVPair kvPair3 = new KVPair();
kvPair3.setKey("label2");
kvPair3.setValue("value2");
rule2.setLabels(Arrays.asList(kvPair2, kvPair3));
// rule3 not matched
Condition condition3 = new Condition();
condition3.setKey("${http.query.type}");
condition3.setOperation(Operation.IN.toString());
condition3.setValues(Collections.singletonList("wx"));
StainingRule.Rule rule3 = new StainingRule.Rule();
rule3.setConditions(Collections.singletonList(condition3));
KVPair kvPair4 = new KVPair();
kvPair4.setKey("label3");
kvPair4.setValue("value3");
rule3.setLabels(Collections.singletonList(kvPair4));
StainingRule stainingRule = new StainingRule();
stainingRule.setRules(Arrays.asList(rule1, rule2, rule3));
MockServerHttpRequest request = MockServerHttpRequest.get("/users")
.queryParam("source", "wx")
.header("uid", "1000").build();
MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build();
RuleStainingExecutor executor = new RuleStainingExecutor();
Map<String, String> 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"));
}
}

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