From 7c3e9cad42fb83ef8d5a7d55e3d152dcdfb5f992 Mon Sep 17 00:00:00 2001 From: shedfreewu Date: Tue, 3 Feb 2026 23:51:28 +0800 Subject: [PATCH 1/7] fix: fix service operator in tsf router. --- .../core/constant/TsfMetadataConstants.java | 5 ++ .../api/plugin/route/RoutingUtils.java | 76 +++++++++++++------ .../consul/service/router/RouterUtils.java | 63 ++++++++------- 3 files changed, 88 insertions(+), 56 deletions(-) diff --git a/polaris-common/polaris-metadata/src/main/java/com/tencent/polaris/metadata/core/constant/TsfMetadataConstants.java b/polaris-common/polaris-metadata/src/main/java/com/tencent/polaris/metadata/core/constant/TsfMetadataConstants.java index d57034909..c328c5002 100644 --- a/polaris-common/polaris-metadata/src/main/java/com/tencent/polaris/metadata/core/constant/TsfMetadataConstants.java +++ b/polaris-common/polaris-metadata/src/main/java/com/tencent/polaris/metadata/core/constant/TsfMetadataConstants.java @@ -83,6 +83,11 @@ public final class TsfMetadataConstants { */ public static final String TSF_PREFER_IPV6 = "TSF_PREFER_IPV6"; + /** + * tsf service tag operator. + */ + public static final String TSF_SERVICE_TAG_OPERATOR = "TSF_SERVICE_TAG_OPERATOR"; + private TsfMetadataConstants() { } } diff --git a/polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/route/RoutingUtils.java b/polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/route/RoutingUtils.java index 84b60ecb5..124b539e1 100644 --- a/polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/route/RoutingUtils.java +++ b/polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/route/RoutingUtils.java @@ -22,7 +22,9 @@ import com.tencent.polaris.api.utils.MapUtils; import com.tencent.polaris.api.utils.RuleUtils; import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.polaris.metadata.core.constant.TsfMetadataConstants; import com.tencent.polaris.metadata.core.manager.MetadataContainerGroup; +import com.tencent.polaris.specification.api.v1.model.ModelProto; import com.tencent.polaris.specification.api.v1.traffic.manage.RoutingProto; import java.util.HashMap; @@ -49,34 +51,62 @@ public static boolean matchSourceService(RoutingProto.Source ruleSource, Service .equals(ruleSource.getService().getValue())) { return false; } + return true; } else { - // 如果有source服务信息, 需要匹配服务信息 - // 如果命名空间|服务不为"*"且不等于原服务, 则匹配失败 - String namespace = ruleSource.getNamespace().getValue(); - if (!RuleUtils.MATCH_ALL.equals(namespace) - && !StringUtils.equals(namespace, targetSourceService.getNamespace())) { - return false; + if (ruleSource.containsMetadata(TsfMetadataConstants.TSF_SERVICE_TAG_OPERATOR)) { + ModelProto.MatchString.MatchStringType matchType = ruleSource.getMetadataMap().get(TsfMetadataConstants.TSF_SERVICE_TAG_OPERATOR).getType(); + return matchTsfSourceService(matchType, ruleSource, targetSourceService); + } else { + return matchPolarisSourceService(ruleSource, targetSourceService); } - String service = ruleSource.getService().getValue(); - if (!RuleUtils.MATCH_ALL.equals(service) && !StringUtils.startsWith(service, "!") - && !StringUtils.startsWith(service, "*") - && !StringUtils.equals(service, targetSourceService.getService())) { + } + } + + private static boolean matchTsfSourceService(ModelProto.MatchString.MatchStringType matchType, RoutingProto.Source ruleSource, Service targetSourceService) { + // 如果有source服务信息, 需要匹配服务信息 + // 如果命名空间|服务不为"*"且不等于原服务, 则匹配失败 + String namespace = ruleSource.getNamespace().getValue(); + if (!RuleUtils.MATCH_ALL.equals(namespace) + && !StringUtils.equals(namespace, targetSourceService.getNamespace())) { + switch (matchType) { + case NOT_EQUALS: + case NOT_IN: + return true; + default: return false; } - // 如果服务名不等于“*”,且服务名规则以“!”开头,则使用取反匹配 - if (!RuleUtils.MATCH_ALL.equals(service) && StringUtils.startsWith(service, "!")) { - String realService = StringUtils.substring(service, 1); - if (StringUtils.equals(realService, targetSourceService.getService())) { - return false; - } + } + return RuleUtils.matchStringValue(matchType, targetSourceService.getService(), + ruleSource.getService().getValue()); + } + + private static boolean matchPolarisSourceService(RoutingProto.Source ruleSource, Service targetSourceService) { + // 如果有source服务信息, 需要匹配服务信息 + // 如果命名空间|服务不为"*"且不等于原服务, 则匹配失败 + String namespace = ruleSource.getNamespace().getValue(); + if (!RuleUtils.MATCH_ALL.equals(namespace) + && !StringUtils.equals(namespace, targetSourceService.getNamespace())) { + return false; + } + String service = ruleSource.getService().getValue(); + if (!RuleUtils.MATCH_ALL.equals(service) && !StringUtils.startsWith(service, "!") + && !StringUtils.startsWith(service, "*") + && !StringUtils.equals(service, targetSourceService.getService())) { + return false; + } + // 如果服务名不等于“*”,且服务名规则以“!”开头,则使用取反匹配 + if (!RuleUtils.MATCH_ALL.equals(service) && StringUtils.startsWith(service, "!")) { + String realService = StringUtils.substring(service, 1); + if (StringUtils.equals(realService, targetSourceService.getService())) { + return false; } - // 如果服务名不等于“*”,且服务名规则以“*”开头,则使用正则匹配 - if (!RuleUtils.MATCH_ALL.equals(service) && StringUtils.startsWith(service, "*")) { - String regex = StringUtils.substring(service, 1); - Pattern pattern = Pattern.compile(regex); - if (!pattern.matcher(targetSourceService.getService()).find()) { - return false; - } + } + // 如果服务名不等于“*”,且服务名规则以“*”开头,则使用正则匹配 + if (!RuleUtils.MATCH_ALL.equals(service) && StringUtils.startsWith(service, "*")) { + String regex = StringUtils.substring(service, 1); + Pattern pattern = Pattern.compile(regex); + if (!pattern.matcher(targetSourceService.getService()).find()) { + return false; } } return true; diff --git a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RouterUtils.java b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RouterUtils.java index 822ee9828..c71411db8 100644 --- a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RouterUtils.java +++ b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RouterUtils.java @@ -20,6 +20,8 @@ import com.google.protobuf.StringValue; import com.tencent.polaris.api.utils.CollectionUtils; import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.polaris.metadata.core.constant.TsfMetadataConstants; +import com.tencent.polaris.plugins.connector.consul.service.common.TagConditionUtil; import com.tencent.polaris.plugins.connector.consul.service.common.TagConstant; import com.tencent.polaris.plugins.connector.consul.service.router.entity.RouteTag; import com.tencent.polaris.specification.api.v1.model.ModelProto; @@ -43,42 +45,37 @@ public static List parseTagListToSourceList(List List metadataSourceBuilders = new ArrayList<>(); for (RouteTag routeTag : tagList) { if (StringUtils.equals(routeTag.getTagField(), TagConstant.SYSTEM_FIELD.SOURCE_SERVICE_NAME)) { - String[] tagValues = routeTag.getTagValue().split(","); - for (String tagValue : tagValues) { - if (StringUtils.isNotEmpty(tagValue)) { - RoutingProto.Source.Builder sourceBuilder = RoutingProto.Source.newBuilder(); - sourceBuilder.setNamespace(StringValue.of("*")); - String serviceName = tagValue; - if (routeTag.getTagOperator().equals(TagConstant.OPERATOR.NOT_EQUAL) || routeTag.getTagOperator().equals(TagConstant.OPERATOR.NOT_IN)) { - serviceName = "!" + serviceName; - } else if (routeTag.getTagOperator().equals(TagConstant.OPERATOR.REGEX)) { - serviceName = "*" + serviceName; - } - sourceBuilder.setService(StringValue.of(serviceName)); - sourceBuilders.add(sourceBuilder); - } + String tagValue = routeTag.getTagValue(); + if (StringUtils.isNotEmpty(tagValue)) { + RoutingProto.Source.Builder sourceBuilder = RoutingProto.Source.newBuilder(); + sourceBuilder.setNamespace(StringValue.of("*")); + sourceBuilder.setService(StringValue.of(tagValue)); + + ModelProto.MatchString.Builder matchStringBuilder = ModelProto.MatchString.newBuilder(); + matchStringBuilder.setType(TagConditionUtil.parseMatchStringType(routeTag.getTagOperator())); + sourceBuilder.putMetadata(TsfMetadataConstants.TSF_SERVICE_TAG_OPERATOR, matchStringBuilder.build()); + + sourceBuilders.add(sourceBuilder); } } else if (StringUtils.equals(routeTag.getTagField(), TagConstant.SYSTEM_FIELD.SOURCE_NAMESPACE_SERVICE_NAME)) { - String[] tagValues = routeTag.getTagValue().split(","); - for (String tagValue : tagValues) { - if (StringUtils.isNotEmpty(tagValue)) { - String[] split = tagValue.split("/"); - RoutingProto.Source.Builder sourceBuilder = RoutingProto.Source.newBuilder(); - sourceBuilder.setNamespace(StringValue.of("*")); - String serviceName = tagValue; - if (split.length == 2) { - // namespace/service format - sourceBuilder.setNamespace(StringValue.of(split[0])); - serviceName = split[1]; - } - if (routeTag.getTagOperator().equals(TagConstant.OPERATOR.NOT_EQUAL) || routeTag.getTagOperator().equals(TagConstant.OPERATOR.NOT_IN)) { - serviceName = "!" + serviceName; - } else if (routeTag.getTagOperator().equals(TagConstant.OPERATOR.REGEX)) { - serviceName = "*" + serviceName; - } - sourceBuilder.setService(StringValue.of(serviceName)); - sourceBuilders.add(sourceBuilder); + String tagValue = routeTag.getTagValue(); + if (StringUtils.isNotEmpty(tagValue)) { + String[] split = tagValue.split("/"); + RoutingProto.Source.Builder sourceBuilder = RoutingProto.Source.newBuilder(); + sourceBuilder.setNamespace(StringValue.of("*")); + String serviceName = tagValue; + if (split.length == 2) { + // namespace/service format + sourceBuilder.setNamespace(StringValue.of(split[0])); + serviceName = split[1]; } + sourceBuilder.setService(StringValue.of(serviceName)); + + ModelProto.MatchString.Builder matchStringBuilder = ModelProto.MatchString.newBuilder(); + matchStringBuilder.setType(TagConditionUtil.parseMatchStringType(routeTag.getTagOperator())); + sourceBuilder.putMetadata(TsfMetadataConstants.TSF_SERVICE_TAG_OPERATOR, matchStringBuilder.build()); + + sourceBuilders.add(sourceBuilder); } } else { RoutingProto.Source.Builder metadataSourceBuilder = RoutingProto.Source.newBuilder(); From 8342a1ecd7add368b60be9e8fca7ef15b2d63769 Mon Sep 17 00:00:00 2001 From: shedfreewu Date: Wed, 4 Feb 2026 12:03:17 +0800 Subject: [PATCH 2/7] add ut --- .../consul/service/router/RouterUtils.java | 5 + .../TsfRuleBasedRouterIntegrationTest.java | 660 ++++++++++++++++++ 2 files changed, 665 insertions(+) create mode 100644 polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java diff --git a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RouterUtils.java b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RouterUtils.java index c71411db8..391e88a9f 100644 --- a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RouterUtils.java +++ b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RouterUtils.java @@ -19,6 +19,7 @@ import com.google.protobuf.StringValue; import com.tencent.polaris.api.utils.CollectionUtils; +import com.tencent.polaris.api.utils.RuleUtils; import com.tencent.polaris.api.utils.StringUtils; import com.tencent.polaris.metadata.core.constant.TsfMetadataConstants; import com.tencent.polaris.plugins.connector.consul.service.common.TagConditionUtil; @@ -54,6 +55,8 @@ public static List parseTagListToSourceList(List ModelProto.MatchString.Builder matchStringBuilder = ModelProto.MatchString.newBuilder(); matchStringBuilder.setType(TagConditionUtil.parseMatchStringType(routeTag.getTagOperator())); sourceBuilder.putMetadata(TsfMetadataConstants.TSF_SERVICE_TAG_OPERATOR, matchStringBuilder.build()); + // TSF 服务名匹配时无 metadata, 设置 *, 跳过 RuleUtils.matchMetadata + sourceBuilder.putMetadata(RuleUtils.MATCH_ALL, ModelProto.MatchString.newBuilder().build()); sourceBuilders.add(sourceBuilder); } @@ -74,6 +77,8 @@ public static List parseTagListToSourceList(List ModelProto.MatchString.Builder matchStringBuilder = ModelProto.MatchString.newBuilder(); matchStringBuilder.setType(TagConditionUtil.parseMatchStringType(routeTag.getTagOperator())); sourceBuilder.putMetadata(TsfMetadataConstants.TSF_SERVICE_TAG_OPERATOR, matchStringBuilder.build()); + // TSF 服务名匹配时无 metadata, 设置 *, 跳过 RuleUtils.matchMetadata + sourceBuilder.putMetadata(RuleUtils.MATCH_ALL, ModelProto.MatchString.newBuilder().build()); sourceBuilders.add(sourceBuilder); } diff --git a/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java b/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java new file mode 100644 index 000000000..a80c85109 --- /dev/null +++ b/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java @@ -0,0 +1,660 @@ +/* + * Tencent is pleased to support the open source community by making polaris-java available. + * + * Copyright (C) 2021 Tencent. 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. + */ + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.protobuf.StringValue; +import com.google.protobuf.UInt32Value; +import com.tencent.polaris.api.config.Configuration; +import com.tencent.polaris.api.core.ConsumerAPI; +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.api.pojo.ServiceKey; +import com.tencent.polaris.api.rpc.GetAllInstancesRequest; +import com.tencent.polaris.api.rpc.InstancesResponse; +import com.tencent.polaris.api.utils.RuleUtils; +import com.tencent.polaris.client.api.BaseEngine; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.client.pojo.Node; +import com.tencent.polaris.factory.api.DiscoveryAPIFactory; +import com.tencent.polaris.factory.api.RouterAPIFactory; +import com.tencent.polaris.metadata.core.MetadataContainer; +import com.tencent.polaris.metadata.core.MetadataType; +import com.tencent.polaris.metadata.core.TransitiveType; +import com.tencent.polaris.metadata.core.manager.MetadataContext; +import com.tencent.polaris.metadata.core.manager.MetadataContextHolder; +import com.tencent.polaris.router.api.core.RouterAPI; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; +import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse; +import com.tencent.polaris.specification.api.v1.model.ModelProto; +import com.tencent.polaris.specification.api.v1.traffic.manage.RoutingProto; +import com.tencent.polaris.test.common.TestUtils; +import com.tencent.polaris.test.mock.discovery.NamingServer; +import com.tencent.polaris.test.mock.discovery.NamingService.InstanceParameter; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static com.tencent.polaris.metadata.core.constant.TsfMetadataConstants.TSF_APPLICATION_ID; +import static com.tencent.polaris.metadata.core.constant.TsfMetadataConstants.TSF_GROUP_ID; +import static com.tencent.polaris.metadata.core.constant.TsfMetadataConstants.TSF_NAMESPACE_ID; +import static com.tencent.polaris.metadata.core.constant.TsfMetadataConstants.TSF_SERVICE_TAG_OPERATOR; + +/** + * RuleBasedRouter in TSF 集成测试 + * + * 场景说明: + * - caller (主调服务) 作为主调服务 + * - callee (被调服务) 作为被调服务 + */ +public class TsfRuleBasedRouterIntegrationTest { + + private static final String POLARIS_SERVER_ADDRESS_PROPERTY = "POLARIS_SEVER_ADDRESS"; + private static final String NAMESPACE = "namespace-xxx"; + private static final String CALLER_SERVICE = "CallerService"; // 主调服务 + private static final String CALLEE_SERVICE = "CalleeService"; // 被调服务 + + private NamingServer namingServer; + private SDKContext sdkContext; + private RouterAPI routerAPI; + private ConsumerAPI consumerAPI; + private ServiceKey callerServiceKey; // 主调服务 key + private ServiceKey calleeServiceKey; // 被调服务 key + + private String callerApplicationId = "application-id-caller-1"; + private String callerGroupId = "group-id-caller-1"; + private String insCalleeApplicationId = "application-id-callee-1"; + private String ruleCalleeApplicationId = insCalleeApplicationId; + private String insCalleeGroupId1 = "group-id-callee-1"; + private String insCalleeGroupId2 = "group-id-callee-2"; + private String ruleCalleeGroupId = insCalleeGroupId1; + + @Before + public void setUp() { + try { + // 1. 启动 Mock NamingServer + namingServer = NamingServer.startNamingServer(-1); + System.setProperty(POLARIS_SERVER_ADDRESS_PROPERTY, String.format("127.0.0.1:%d", namingServer.getPort())); + + // 2. 创建 SDK 上下文和 API + Configuration configuration = TestUtils.configWithEnvAddress(); + + sdkContext = SDKContext.initContextByConfig(configuration); + routerAPI = RouterAPIFactory.createRouterAPIByContext(sdkContext); + consumerAPI = DiscoveryAPIFactory.createConsumerAPIByContext(sdkContext); + + // 3. 初始化服务 Key + callerServiceKey = new ServiceKey(NAMESPACE, CALLER_SERVICE); + calleeServiceKey = new ServiceKey(NAMESPACE, CALLEE_SERVICE); + + // 4. 注册 callee 服务实例(被调服务需要有实例供路由选择) + registerCalleeServiceInstances(); + + } catch (IOException e) { + Assert.fail("Failed to start NamingServer: " + e.getMessage()); + } + } + + /** + * 注册 callee 服务实例 + * 只有被调服务(callee)需要注册实例,因为路由选择的是下游服务的实例 + */ + private void registerCalleeServiceInstances() { + + InstanceParameter parameter = new InstanceParameter(); + parameter.setWeight(100); + parameter.setHealthy(true); + parameter.setIsolated(false); + Map meta = new HashMap<>(); + meta.put(TSF_NAMESPACE_ID, NAMESPACE); + meta.put(TSF_APPLICATION_ID, insCalleeApplicationId); + meta.put(TSF_GROUP_ID, insCalleeGroupId1); + parameter.setMetadata(meta); + namingServer.getNamingService().addInstance(calleeServiceKey, new Node("127.0.0.1", 8080), parameter); + namingServer.getNamingService().addInstance(calleeServiceKey, new Node("127.0.0.1", 8081), parameter); + + InstanceParameter parameter2 = new InstanceParameter(); + parameter2.setWeight(100); + parameter2.setHealthy(true); + parameter2.setIsolated(false); + Map meta2 = new HashMap<>(); + meta2.put(TSF_NAMESPACE_ID, NAMESPACE); + meta2.put(TSF_APPLICATION_ID, insCalleeApplicationId); + meta2.put(TSF_GROUP_ID, insCalleeGroupId2); + parameter2.setMetadata(meta2); + namingServer.getNamingService().addInstance(calleeServiceKey, new Node("127.0.0.1", 9080), parameter2); + namingServer.getNamingService().addInstance(calleeServiceKey, new Node("127.0.0.1", 9081), parameter2); + } + + @After + public void tearDown() { + if (namingServer != null) { + namingServer.terminate(); + System.clearProperty(POLARIS_SERVER_ADDRESS_PROPERTY); + } + if (sdkContext != null) { + sdkContext.destroy(); + } + if (routerAPI != null) { + ((BaseEngine) routerAPI).destroy(); + } + if (consumerAPI != null) { + consumerAPI.destroy(); + } + // 清理 MetadataContextHolder 中的线程本地变量 + MetadataContextHolder.remove(); + } + + @Test + public void testRouting1() throws InterruptedException { + // 设置规则:NOT_IN + 多个服务名逗号分隔 + 不匹配(测试多个服务名,当前上游服务名在里面的情况) + setupSourceServiceNameRules(CALLER_SERVICE + ",mock-service", "*", ModelProto.MatchString.MatchStringType.NOT_IN); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果 + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(4, routedInstances.size()); + } + } + + @Test + public void testRouting2() throws InterruptedException { + // 设置规则:NOT_IN + 多个服务名 + 匹配(测试多个服务名,当前上游服务名不在里面的情况) + setupSourceServiceNameRules("mock-service,mock-service2", "*", ModelProto.MatchString.MatchStringType.NOT_IN); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回基线实例 + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(2, routedInstances.size()); + + int[] ports = routedInstances.stream().mapToInt(Instance::getPort).sorted().toArray(); + Assert.assertArrayEquals(new int[] {8080, 8081}, ports); + } + } + + @Test + public void testRouting3() throws InterruptedException { + // 设置规则:EXACT + SOURCE_SERVICE_NAME + 匹配(精确匹配CallerService) + setupSourceServiceNameRules(CALLER_SERVICE, "*", ModelProto.MatchString.MatchStringType.EXACT); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回基线实例 + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(2, routedInstances.size()); + + int[] ports = routedInstances.stream().mapToInt(Instance::getPort).sorted().toArray(); + Assert.assertArrayEquals(new int[] {8080, 8081}, ports); + } + } + + @Test + public void testRouting4() throws InterruptedException { + // 设置规则:REGEX + SOURCE_SERVICE_NAME + 匹配(正则表达式匹配CallerService) + setupSourceServiceNameRules("Caller.*", "*", ModelProto.MatchString.MatchStringType.REGEX); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回基线实例 + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(2, routedInstances.size()); + + int[] ports = routedInstances.stream().mapToInt(Instance::getPort).sorted().toArray(); + Assert.assertArrayEquals(new int[] {8080, 8081}, ports); + } + } + + @Test + public void testRouting5() throws InterruptedException { + // 设置规则:REGEX + SOURCE_SERVICE_NAME + 不匹配(正则表达式不匹配CallerService) + setupSourceServiceNameRules("MockService.*", "*", ModelProto.MatchString.MatchStringType.REGEX); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回所有实例(因为正则不匹配,规则不生效) + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(4, routedInstances.size()); + } + } + + @Test + public void testRouting6() throws InterruptedException { + // 设置规则:IN + SOURCE_SERVICE_NAME + 匹配(在列表中匹配) + setupSourceServiceNameRules("CallerService,MockService", "*", ModelProto.MatchString.MatchStringType.IN); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回基线实例 + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(2, routedInstances.size()); + + int[] ports = routedInstances.stream().mapToInt(Instance::getPort).sorted().toArray(); + Assert.assertArrayEquals(new int[] {8080, 8081}, ports); + } + } + + @Test + public void testRouting7() throws InterruptedException { + // 设置规则:IN + SOURCE_SERVICE_NAME + 不匹配(不在列表中) + setupSourceServiceNameRules("MockService1,MockService2", "*", ModelProto.MatchString.MatchStringType.IN); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回所有实例(因为不在列表中,规则不生效) + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(4, routedInstances.size()); + } + } + + @Test + public void testRouting8() throws InterruptedException { + // 设置规则:NOT_EQUALS + SOURCE_SERVICE_NAME + 匹配(不等于指定服务名) + setupSourceServiceNameRules("MockService", "*", ModelProto.MatchString.MatchStringType.NOT_EQUALS); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回基线实例(因为CallerService不等于MockService) + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(2, routedInstances.size()); + + int[] ports = routedInstances.stream().mapToInt(Instance::getPort).sorted().toArray(); + Assert.assertArrayEquals(new int[] {8080, 8081}, ports); + } + } + + @Test + public void testRouting9() throws InterruptedException { + // 设置规则:NOT_EQUALS + SOURCE_SERVICE_NAME + 不匹配(等于指定服务名) + setupSourceServiceNameRules(CALLER_SERVICE, "*", ModelProto.MatchString.MatchStringType.NOT_EQUALS); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回所有实例(因为等于指定服务名,规则不生效) + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(4, routedInstances.size()); + } + } + + @Test + public void testRouting10() throws InterruptedException { + // 设置规则:空服务名 + 边界情况测试 + setupSourceServiceNameRules("", "*", ModelProto.MatchString.MatchStringType.EXACT); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:空服务名可能触发默认路由行为,返回基线实例 + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(2, routedInstances.size()); + + int[] ports = routedInstances.stream().mapToInt(Instance::getPort).sorted().toArray(); + Assert.assertArrayEquals(new int[] {8080, 8081}, ports); + } + } + + @Test + public void testRouting11() throws InterruptedException { + // 设置规则:通配符 * + 全匹配(测试边界情况) + setupSourceServiceNameRules("*", "*", ModelProto.MatchString.MatchStringType.EXACT); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回基线实例(通配符匹配所有服务) + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(2, routedInstances.size()); + + int[] ports = routedInstances.stream().mapToInt(Instance::getPort).sorted().toArray(); + Assert.assertArrayEquals(new int[] {8080, 8081}, ports); + } + } + + @Test + public void testRouting12() throws InterruptedException { + // 设置规则:EXACT + 不同命名空间 + 不匹配(测试命名空间隔离) + setupSourceServiceNameRules(CALLER_SERVICE, "different-namespace", ModelProto.MatchString.MatchStringType.EXACT); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回所有实例(因为命名空间不匹配,规则不生效) + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(4, routedInstances.size()); + } + } + + @Test + public void testRouting13() throws InterruptedException { + // 设置规则:大小写敏感测试(EXACT匹配默认不区分大小写) + setupSourceServiceNameRules("callerservice", "*", ModelProto.MatchString.MatchStringType.EXACT); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回基线实例(EXACT匹配不区分大小写) + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(2, routedInstances.size()); + + int[] ports = routedInstances.stream().mapToInt(Instance::getPort).sorted().toArray(); + Assert.assertArrayEquals(new int[] {8080, 8081}, ports); + } + } + + @Test + public void testRouting14() throws InterruptedException { + // 设置规则:复杂正则表达式匹配(测试正则表达式的边界情况) + setupSourceServiceNameRules("^Caller.*Service$", "*", ModelProto.MatchString.MatchStringType.REGEX); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回基线实例(正则表达式匹配CallerService) + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(2, routedInstances.size()); + + int[] ports = routedInstances.stream().mapToInt(Instance::getPort).sorted().toArray(); + Assert.assertArrayEquals(new int[] {8080, 8081}, ports); + } + } + + @Test + public void testRouting15() throws InterruptedException { + // 设置规则:空规则测试(测试无规则时的默认行为) + // 不设置任何规则,测试默认路由行为 + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果:应该返回所有实例(无规则时默认返回所有可用实例) + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(4, routedInstances.size()); + } + } + + private void setupSourceServiceNameRules(String sourceServiceName, String sourceNamespace, ModelProto.MatchString.MatchStringType matchType) { + RoutingProto.Source source = RoutingProto.Source.newBuilder() + .setService(StringValue.newBuilder().setValue(sourceServiceName)) + .setNamespace(StringValue.newBuilder().setValue(sourceNamespace)) + .putMetadata(TSF_SERVICE_TAG_OPERATOR, ModelProto.MatchString.newBuilder().setType(matchType).build()) + .putMetadata(RuleUtils.MATCH_ALL, ModelProto.MatchString.newBuilder().build()) + .build(); + + RoutingProto.Destination destination = RoutingProto.Destination.newBuilder() + .setNamespace(StringValue.newBuilder().setValue(NAMESPACE).build()) + .setService(StringValue.newBuilder().setValue(CALLEE_SERVICE).build()) + .putMetadata(TSF_GROUP_ID, ModelProto.MatchString.newBuilder().setValue(StringValue.newBuilder().setValue(ruleCalleeGroupId)).build()) + .setWeight(UInt32Value.newBuilder().setValue(100).build()) + .build(); + + RoutingProto.Routing routing = RoutingProto.Routing.newBuilder() + .setService(StringValue.newBuilder().setValue(CALLEE_SERVICE).build()) + .setNamespace(StringValue.newBuilder().setValue(NAMESPACE).build()) + .addInbounds(RoutingProto.Route.newBuilder().addDestinations(destination) + .addSources(source).build()) + .build(); + + namingServer.getNamingService().setRouting(calleeServiceKey, routing); + } + + // ==================== 辅助方法 ==================== + + /** + * 获取指定服务的所有实例 + */ + private ServiceInstances getAllInstances(String serviceName) { + GetAllInstancesRequest request = new GetAllInstancesRequest(); + request.setNamespace(NAMESPACE); + request.setService(serviceName); + InstancesResponse response = consumerAPI.getAllInstances(request); + return response.getServiceInstances(); + } + + /** + * 构建路由请求 + * 模拟 caller(主调服务)调用 callee(被调服务) + * + * Header 需要设置在 MetadataContext 的 MessageMetadataContainer 中, + * 而不是通过 addRouterMetadata 方法 + */ + private ProcessRoutersRequest buildRouterRequest(ServiceInstances serviceInstances, + String key, + String value) { + // 先清理之前的 MetadataContext,确保每次测试使用干净的上下文 + MetadataContextHolder.remove(); + + ProcessRoutersRequest request = new ProcessRoutersRequest(); + request.setNamespace(NAMESPACE); + request.setService(CALLEE_SERVICE); // 目标服务为 callee + + // 设置源服务信息(caller - 主调服务) + ServiceInfo sourceService = new ServiceInfo(); + sourceService.setNamespace(NAMESPACE); + sourceService.setService(CALLER_SERVICE); // 源服务为 caller + request.setSourceService(sourceService); + + // 设置目标实例(callee 的实例) + request.setDstInstances(serviceInstances); + + // 通过 MetadataContextHolder 设置 header 到 MetadataContainer + // LaneRouter 会从 MetadataContext 的 caller/callee MetadataContainer 中获取 key + if (key != null && value != null) { + MetadataContext metadataContext = MetadataContextHolder.getOrCreate(); + // 设置到 caller(主调方)的 MetadataContainer + MetadataContainer calleeMessageContainer = metadataContext.getMetadataContainer(MetadataType.CUSTOM, false); + + calleeMessageContainer.putMetadataStringValue(key, value, TransitiveType.PASS_THROUGH); + } + + return request; + } +} \ No newline at end of file From 64abb8010b130f58eb0f999510dffb12b50d9ecb Mon Sep 17 00:00:00 2001 From: shedfreewu Date: Wed, 4 Feb 2026 14:55:10 +0800 Subject: [PATCH 3/7] fix ut --- .../TsfRuleBasedRouterIntegrationTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java b/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java index a80c85109..48bbbebe2 100644 --- a/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java +++ b/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java @@ -209,7 +209,7 @@ public void testRouting2() throws InterruptedException { // 执行路由 ProcessRoutersResponse response = routerAPI.processRouters(request); - // 验证结果:应该返回基线实例 + // 验证结果:应该返回 group1 实例 List routedInstances = response.getServiceInstances().getInstances(); Assert.assertNotNull(routedInstances); Assert.assertEquals(2, routedInstances.size()); @@ -238,7 +238,7 @@ public void testRouting3() throws InterruptedException { // 执行路由 ProcessRoutersResponse response = routerAPI.processRouters(request); - // 验证结果:应该返回基线实例 + // 验证结果:应该返回 group1 实例 List routedInstances = response.getServiceInstances().getInstances(); Assert.assertNotNull(routedInstances); Assert.assertEquals(2, routedInstances.size()); @@ -267,7 +267,7 @@ public void testRouting4() throws InterruptedException { // 执行路由 ProcessRoutersResponse response = routerAPI.processRouters(request); - // 验证结果:应该返回基线实例 + // 验证结果:应该返回 group1 实例 List routedInstances = response.getServiceInstances().getInstances(); Assert.assertNotNull(routedInstances); Assert.assertEquals(2, routedInstances.size()); @@ -322,7 +322,7 @@ public void testRouting6() throws InterruptedException { // 执行路由 ProcessRoutersResponse response = routerAPI.processRouters(request); - // 验证结果:应该返回基线实例 + // 验证结果:应该返回 group1 实例 List routedInstances = response.getServiceInstances().getInstances(); Assert.assertNotNull(routedInstances); Assert.assertEquals(2, routedInstances.size()); @@ -377,7 +377,7 @@ public void testRouting8() throws InterruptedException { // 执行路由 ProcessRoutersResponse response = routerAPI.processRouters(request); - // 验证结果:应该返回基线实例(因为CallerService不等于MockService) + // 验证结果:应该返回 group1 实例(因为CallerService不等于MockService) List routedInstances = response.getServiceInstances().getInstances(); Assert.assertNotNull(routedInstances); Assert.assertEquals(2, routedInstances.size()); @@ -415,7 +415,7 @@ public void testRouting9() throws InterruptedException { @Test public void testRouting10() throws InterruptedException { - // 设置规则:空服务名 + 边界情况测试 + // 设置规则:空服务名 + 边界情况测试, 空服务名与 * 一样,满足 RuleUtils.isMatchAllValue setupSourceServiceNameRules("", "*", ModelProto.MatchString.MatchStringType.EXACT); // 等待服务发现数据同步 @@ -432,7 +432,7 @@ public void testRouting10() throws InterruptedException { // 执行路由 ProcessRoutersResponse response = routerAPI.processRouters(request); - // 验证结果:空服务名可能触发默认路由行为,返回基线实例 + // 验证结果:应该返回 group1 实例 List routedInstances = response.getServiceInstances().getInstances(); Assert.assertNotNull(routedInstances); Assert.assertEquals(2, routedInstances.size()); @@ -461,7 +461,7 @@ public void testRouting11() throws InterruptedException { // 执行路由 ProcessRoutersResponse response = routerAPI.processRouters(request); - // 验证结果:应该返回基线实例(通配符匹配所有服务) + // 验证结果:应该返回 group1 实例(通配符匹配所有服务) List routedInstances = response.getServiceInstances().getInstances(); Assert.assertNotNull(routedInstances); Assert.assertEquals(2, routedInstances.size()); @@ -516,7 +516,7 @@ public void testRouting13() throws InterruptedException { // 执行路由 ProcessRoutersResponse response = routerAPI.processRouters(request); - // 验证结果:应该返回基线实例(EXACT匹配不区分大小写) + // 验证结果:应该返回 group1 实例(EXACT匹配不区分大小写) List routedInstances = response.getServiceInstances().getInstances(); Assert.assertNotNull(routedInstances); Assert.assertEquals(2, routedInstances.size()); @@ -545,7 +545,7 @@ public void testRouting14() throws InterruptedException { // 执行路由 ProcessRoutersResponse response = routerAPI.processRouters(request); - // 验证结果:应该返回基线实例(正则表达式匹配CallerService) + // 验证结果:应该返回 group1 实例(正则表达式匹配CallerService) List routedInstances = response.getServiceInstances().getInstances(); Assert.assertNotNull(routedInstances); Assert.assertEquals(2, routedInstances.size()); From 716981a6717ac5895f7fafe9a0fca4b833f74742 Mon Sep 17 00:00:00 2001 From: shedfreewu Date: Wed, 4 Feb 2026 23:47:51 +0800 Subject: [PATCH 4/7] add --- .../fault/client/flow/DefaultFaultFlow.java | 16 ++- .../api/plugin/route/RouterConstants.java | 3 + .../consul/service/fault/FaultService.java | 2 + .../service/mirroring/MirroringService.java | 3 +- .../consul/service/router/RouterUtils.java | 34 ++--- .../consul/service/router/RoutingService.java | 4 +- .../mirroring/TrafficMirroringRouter.java | 15 ++- .../plugins/router/rule/RuleBasedRouter.java | 19 ++- .../TsfRuleBasedRouterIntegrationTest.java | 120 ++++++++++++++---- 9 files changed, 151 insertions(+), 65 deletions(-) diff --git a/polaris-fault/polaris-fault-client/src/main/java/com/tencent/polaris/fault/client/flow/DefaultFaultFlow.java b/polaris-fault/polaris-fault-client/src/main/java/com/tencent/polaris/fault/client/flow/DefaultFaultFlow.java index 66d0f3d92..37b670d2c 100644 --- a/polaris-fault/polaris-fault-client/src/main/java/com/tencent/polaris/fault/client/flow/DefaultFaultFlow.java +++ b/polaris-fault/polaris-fault-client/src/main/java/com/tencent/polaris/fault/client/flow/DefaultFaultFlow.java @@ -22,6 +22,7 @@ import com.tencent.polaris.api.exception.PolarisException; import com.tencent.polaris.api.plugin.cache.FlowCache; import com.tencent.polaris.api.plugin.compose.Extensions; +import com.tencent.polaris.api.plugin.route.RouterConstants; import com.tencent.polaris.api.plugin.route.RoutingUtils; import com.tencent.polaris.api.pojo.*; import com.tencent.polaris.api.rpc.RequestBaseEntity; @@ -88,7 +89,7 @@ public FaultResponse fault(FaultRequest faultRequest) throws PolarisException { LOG.debug("FaultInjectionProto.FaultInjection:{}", faultInjection); // 匹配source规则 - boolean sourceMatched = matchSource(faultInjection.getSourcesList(), + boolean sourceMatched = matchSource(faultInjection, faultRequest.getSourceService(), faultRequest.getMetadataContext().getMetadataContainerGroup(false)); if (!sourceMatched) { LOG.debug("Source not matched, skipping fault injection. FaultInjectionProto.FaultInjection:{}", faultInjection); @@ -158,10 +159,13 @@ public List getFaultInjectionRules(ServiceKe /** * 匹配source规则 */ - private boolean matchSource(List sources, Service sourceService, MetadataContainerGroup metadataContainerGroup) { + private boolean matchSource(FaultInjectionProto.FaultInjection faultInjection, Service sourceService, MetadataContainerGroup metadataContainerGroup) { + List sources = faultInjection.getSourcesList(); + if (CollectionUtils.isEmpty(sources)) { return true; } + boolean matchAllSources = faultInjection.containsMetadata(RouterConstants.MATCH_ALL_SOURCES); // source匹配成功标志 boolean matched = true; for (RoutingProto.Source source : sources) { @@ -173,8 +177,12 @@ private boolean matchSource(List sources, Service sourceSer // 匹配source metadata matched = RoutingUtils.matchSourceMetadata(source, sourceService, metadataContainerGroup, key -> getFlowCache().loadPluginCacheObject(API_ID, key, path -> TrieUtil.buildSimpleApiTrieNode((String) path))); - if (matched) { - break; + if (matchAllSources) { + if (!matched) { + return false; + } + } else if (matched) { + return true; } } return matched; diff --git a/polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/route/RouterConstants.java b/polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/route/RouterConstants.java index 397423747..9ade5b58e 100644 --- a/polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/route/RouterConstants.java +++ b/polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/route/RouterConstants.java @@ -19,6 +19,9 @@ public interface RouterConstants { + // match all sources + String MATCH_ALL_SOURCES = "match-all-sources"; + //set路由 String SET_ENABLE_KEY = "internal-enable-set"; String SET_NAME_KEY = "internal-set-name"; diff --git a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/fault/FaultService.java b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/fault/FaultService.java index 9cadb5863..c627328a2 100644 --- a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/fault/FaultService.java +++ b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/fault/FaultService.java @@ -36,6 +36,7 @@ import com.tencent.polaris.api.exception.PolarisException; import com.tencent.polaris.api.exception.ServerCodes; import com.tencent.polaris.api.exception.ServerErrorResponseException; +import com.tencent.polaris.api.plugin.route.RouterConstants; import com.tencent.polaris.api.plugin.server.ServerEvent; import com.tencent.polaris.api.utils.CollectionUtils; import com.tencent.polaris.logging.LoggerFactory; @@ -196,6 +197,7 @@ private List parseResponse(final HttpRespons continue; } FaultInjectionProto.FaultInjection.Builder faultInjectionBuilder = FaultInjectionProto.FaultInjection.newBuilder(); + faultInjectionBuilder.putMetadata(RouterConstants.MATCH_ALL_SOURCES, "true"); // parse sources List sources = RouterUtils.parseTagListToSourceList(routeRule.getTagList()); faultInjectionBuilder.addAllSources(sources); diff --git a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/mirroring/MirroringService.java b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/mirroring/MirroringService.java index 084277248..12b1433bf 100644 --- a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/mirroring/MirroringService.java +++ b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/mirroring/MirroringService.java @@ -37,6 +37,7 @@ import com.tencent.polaris.api.exception.PolarisException; import com.tencent.polaris.api.exception.ServerCodes; import com.tencent.polaris.api.exception.ServerErrorResponseException; +import com.tencent.polaris.api.plugin.route.RouterConstants; import com.tencent.polaris.api.plugin.server.ServerEvent; import com.tencent.polaris.api.utils.CollectionUtils; import com.tencent.polaris.logging.LoggerFactory; @@ -202,7 +203,7 @@ private List parseResponse(final HttpRes // parse enabled trafficMirroringBuilder.setEnabled(BoolValue.of(mirrorRule.getEnabled())); - + trafficMirroringBuilder.putMetadata(RouterConstants.MATCH_ALL_SOURCES, "true"); // parse sources List sources = RouterUtils.parseTagListToSourceList(routeRule.getTagList()); diff --git a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RouterUtils.java b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RouterUtils.java index 391e88a9f..a00190da1 100644 --- a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RouterUtils.java +++ b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RouterUtils.java @@ -42,8 +42,6 @@ public class RouterUtils { public static List parseTagListToSourceList(List tagList) { List sources = new ArrayList<>(); if (CollectionUtils.isNotEmpty(tagList)) { - List sourceBuilders = new ArrayList<>(); - List metadataSourceBuilders = new ArrayList<>(); for (RouteTag routeTag : tagList) { if (StringUtils.equals(routeTag.getTagField(), TagConstant.SYSTEM_FIELD.SOURCE_SERVICE_NAME)) { String tagValue = routeTag.getTagValue(); @@ -55,10 +53,10 @@ public static List parseTagListToSourceList(List ModelProto.MatchString.Builder matchStringBuilder = ModelProto.MatchString.newBuilder(); matchStringBuilder.setType(TagConditionUtil.parseMatchStringType(routeTag.getTagOperator())); sourceBuilder.putMetadata(TsfMetadataConstants.TSF_SERVICE_TAG_OPERATOR, matchStringBuilder.build()); - // TSF 服务名匹配时无 metadata, 设置 *, 跳过 RuleUtils.matchMetadata + // TSF 服务名匹配时无其他 metadata, 设置 *, 跳过 RuleUtils.matchMetadata sourceBuilder.putMetadata(RuleUtils.MATCH_ALL, ModelProto.MatchString.newBuilder().build()); - sourceBuilders.add(sourceBuilder); + sources.add(sourceBuilder.build()); } } else if (StringUtils.equals(routeTag.getTagField(), TagConstant.SYSTEM_FIELD.SOURCE_NAMESPACE_SERVICE_NAME)) { String tagValue = routeTag.getTagValue(); @@ -77,39 +75,23 @@ public static List parseTagListToSourceList(List ModelProto.MatchString.Builder matchStringBuilder = ModelProto.MatchString.newBuilder(); matchStringBuilder.setType(TagConditionUtil.parseMatchStringType(routeTag.getTagOperator())); sourceBuilder.putMetadata(TsfMetadataConstants.TSF_SERVICE_TAG_OPERATOR, matchStringBuilder.build()); - // TSF 服务名匹配时无 metadata, 设置 *, 跳过 RuleUtils.matchMetadata + // TSF 服务名匹配时无其他 metadata, 设置 *, 跳过 RuleUtils.matchMetadata sourceBuilder.putMetadata(RuleUtils.MATCH_ALL, ModelProto.MatchString.newBuilder().build()); - sourceBuilders.add(sourceBuilder); + sources.add(sourceBuilder.build()); } } else { - RoutingProto.Source.Builder metadataSourceBuilder = RoutingProto.Source.newBuilder(); - metadataSourceBuilder.setNamespace(StringValue.of("*")); - metadataSourceBuilder.setService(StringValue.of("*")); + RoutingProto.Source.Builder sourceBuilder = RoutingProto.Source.newBuilder(); + sourceBuilder.setNamespace(StringValue.of("*")); + sourceBuilder.setService(StringValue.of("*")); ModelProto.MatchString.Builder matchStringBuilder = ModelProto.MatchString.newBuilder(); matchStringBuilder.setType(parseMatchStringType(routeTag)); matchStringBuilder.setValue(StringValue.of(routeTag.getTagValue())); matchStringBuilder.setValueType(ModelProto.MatchString.ValueType.TEXT); String metadataKey = routeTag.getTagField(); - metadataSourceBuilder.putMetadata(parseMetadataKey(metadataKey), matchStringBuilder.build()); - metadataSourceBuilders.add(metadataSourceBuilder); - } - } - if (CollectionUtils.isNotEmpty(sourceBuilders)) { - for (RoutingProto.Source.Builder sourceBuilder : sourceBuilders) { - for (RoutingProto.Source.Builder metadataSourceBuilder : metadataSourceBuilders) { - sourceBuilder.putAllMetadata(metadataSourceBuilder.getMetadataMap()); - } + sourceBuilder.putMetadata(parseMetadataKey(metadataKey), matchStringBuilder.build()); sources.add(sourceBuilder.build()); } - } else { - RoutingProto.Source.Builder sourceBuilder = RoutingProto.Source.newBuilder(); - sourceBuilder.setNamespace(StringValue.of("*")); - sourceBuilder.setService(StringValue.of("*")); - for (RoutingProto.Source.Builder metadataSourceBuilder : metadataSourceBuilders) { - sourceBuilder.putAllMetadata(metadataSourceBuilder.getMetadataMap()); - } - sources.add(sourceBuilder.build()); } } return sources; diff --git a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RoutingService.java b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RoutingService.java index 2c0abfc57..6dccb3538 100644 --- a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RoutingService.java +++ b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RoutingService.java @@ -36,6 +36,7 @@ import com.tencent.polaris.api.exception.PolarisException; import com.tencent.polaris.api.exception.ServerCodes; import com.tencent.polaris.api.exception.ServerErrorResponseException; +import com.tencent.polaris.api.plugin.route.RouterConstants; import com.tencent.polaris.api.plugin.server.ServerEvent; import com.tencent.polaris.api.utils.CollectionUtils; import com.tencent.polaris.logging.LoggerFactory; @@ -202,7 +203,8 @@ private List parseResponse(final HttpResponse response, Stri continue; } RoutingProto.Route.Builder routeBuilder = RoutingProto.Route.newBuilder(); - routeBuilder.putExtendInfo(ROUTER_FAULT_TOLERANCE_ENABLE, String.valueOf(routeRuleGroup.getFallbackStatus())); + routeBuilder.putMetadata(ROUTER_FAULT_TOLERANCE_ENABLE, String.valueOf(routeRuleGroup.getFallbackStatus())); + routeBuilder.putMetadata(RouterConstants.MATCH_ALL_SOURCES, "true"); // parse sources List sources = RouterUtils.parseTagListToSourceList(routeRule.getTagList()); diff --git a/polaris-plugins/polaris-plugins-router/router-mirroring/src/main/java/com/tencent/polaris/plugins/router/mirroring/TrafficMirroringRouter.java b/polaris-plugins/polaris-plugins-router/router-mirroring/src/main/java/com/tencent/polaris/plugins/router/mirroring/TrafficMirroringRouter.java index 2ff5c82f9..f481a605a 100644 --- a/polaris-plugins/polaris-plugins-router/router-mirroring/src/main/java/com/tencent/polaris/plugins/router/mirroring/TrafficMirroringRouter.java +++ b/polaris-plugins/polaris-plugins-router/router-mirroring/src/main/java/com/tencent/polaris/plugins/router/mirroring/TrafficMirroringRouter.java @@ -23,6 +23,7 @@ import com.tencent.polaris.api.plugin.compose.Extensions; import com.tencent.polaris.api.plugin.route.RouteInfo; import com.tencent.polaris.api.plugin.route.RouteResult; +import com.tencent.polaris.api.plugin.route.RouterConstants; import com.tencent.polaris.api.plugin.route.RoutingUtils; import com.tencent.polaris.api.pojo.*; import com.tencent.polaris.api.rpc.RequestBaseEntity; @@ -75,7 +76,7 @@ public RouteResult router(RouteInfo routeInfo, ServiceInstances instances) throw continue; } // 匹配source规则 - boolean sourceMatched = matchSource(trafficMirroring.getSourcesList(), + boolean sourceMatched = matchSource(trafficMirroring, routeInfo.getSourceService(), metadataContainerGroup); if (!sourceMatched) { LOG.debug("Source not matched, skipping traffic mirroring. TrafficMirroringProto.TrafficMirroring:{}", trafficMirroring); @@ -121,10 +122,12 @@ public RouteResult router(RouteInfo routeInfo, ServiceInstances instances) throw /** * 匹配source规则 */ - private boolean matchSource(List sources, Service sourceService, MetadataContainerGroup metadataContainerGroup) { + private boolean matchSource(TrafficMirroringProto.TrafficMirroring trafficMirroring, Service sourceService, MetadataContainerGroup metadataContainerGroup) { + List sources = trafficMirroring.getSourcesList(); if (CollectionUtils.isEmpty(sources)) { return true; } + boolean matchAllSources = trafficMirroring.containsMetadata(RouterConstants.MATCH_ALL_SOURCES); // source匹配成功标志 boolean matched = true; for (RoutingProto.Source source : sources) { @@ -136,8 +139,12 @@ private boolean matchSource(List sources, Service sourceSer // 匹配source metadata matched = RoutingUtils.matchSourceMetadata(source, sourceService, metadataContainerGroup, key -> flowCache.loadPluginCacheObject(API_ID, key, path -> TrieUtil.buildSimpleApiTrieNode((String) path))); - if (matched) { - break; + if (matchAllSources) { + if (!matched) { + return false; + } + } else if (matched) { + return true; } } return matched; diff --git a/polaris-plugins/polaris-plugins-router/router-rule/src/main/java/com/tencent/polaris/plugins/router/rule/RuleBasedRouter.java b/polaris-plugins/polaris-plugins-router/router-rule/src/main/java/com/tencent/polaris/plugins/router/rule/RuleBasedRouter.java index 2c4922d32..bf5334302 100644 --- a/polaris-plugins/polaris-plugins-router/router-rule/src/main/java/com/tencent/polaris/plugins/router/rule/RuleBasedRouter.java +++ b/polaris-plugins/polaris-plugins-router/router-rule/src/main/java/com/tencent/polaris/plugins/router/rule/RuleBasedRouter.java @@ -29,6 +29,7 @@ import com.tencent.polaris.api.plugin.compose.Extensions; import com.tencent.polaris.api.plugin.route.RouteInfo; import com.tencent.polaris.api.plugin.route.RouteResult; +import com.tencent.polaris.api.plugin.route.RouterConstants; import com.tencent.polaris.api.plugin.route.RoutingUtils; import com.tencent.polaris.api.plugin.route.ServiceRouter; import com.tencent.polaris.api.pojo.Instance; @@ -106,13 +107,17 @@ private List getRoutesFromRule(RouteInfo routeInfo, RuleMatc } // 匹配source规则 - private boolean matchSource(List sources, Service sourceService, + private boolean matchSource(RoutingProto.Route route, Service sourceService, Map trafficLabels, MetadataContainerGroup metadataContainerGroup, RuleMatchType ruleMatchType, Map multiEnvRouterParamMap) { + + List sources = route.getSourcesList(); + if (CollectionUtils.isEmpty(sources)) { return true; } + boolean matchAllSources = route.containsMetadata(RouterConstants.MATCH_ALL_SOURCES); // source匹配成功标志 boolean matched = true; @@ -128,8 +133,12 @@ private boolean matchSource(List sources, Service sourceSer matched = RoutingUtils.matchSourceMetadata(source, sourceService, trafficLabels, metadataContainerGroup, multiEnvRouterParamMap, globalVariablesConfig, key -> flowCache.loadPluginCacheObject(API_ID, key, path -> TrieUtil.buildSimpleApiTrieNode((String) path))); - if (matched) { - break; + if (matchAllSources) { + if (!matched) { + return false; + } + } else if (matched) { + return true; } } @@ -162,7 +171,7 @@ private List getRuleFilteredInstances(RouteInfo routeInfo, ServiceInst if (route == null) { continue; } else { - matchStatus.fallback = Boolean.parseBoolean(route.getExtendInfoMap().get(ROUTER_FAULT_TOLERANCE_ENABLE)); + matchStatus.fallback = Boolean.parseBoolean(route.getMetadataMap().get(ROUTER_FAULT_TOLERANCE_ENABLE)); } if (LOG.isDebugEnabled()) { @@ -172,7 +181,7 @@ private List getRuleFilteredInstances(RouteInfo routeInfo, ServiceInst Map trafficLabels = routeInfo.getRouterMetadata(ROUTER_TYPE_RULE_BASED); MetadataContainerGroup metadataContainerGroup = routeInfo.getMetadataContainerGroup(); // 匹配source规则 - boolean sourceMatched = matchSource(route.getSourcesList(), routeInfo.getSourceService(), trafficLabels, + boolean sourceMatched = matchSource(route, routeInfo.getSourceService(), trafficLabels, metadataContainerGroup, ruleMatchType, multiEnvRouterParamMap); if (!sourceMatched) { continue; diff --git a/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java b/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java index 48bbbebe2..49fa5b510 100644 --- a/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java +++ b/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java @@ -16,6 +16,8 @@ */ import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -24,6 +26,7 @@ import com.google.protobuf.UInt32Value; import com.tencent.polaris.api.config.Configuration; import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.plugin.route.RouterConstants; import com.tencent.polaris.api.pojo.Instance; import com.tencent.polaris.api.pojo.ServiceInfo; import com.tencent.polaris.api.pojo.ServiceInstances; @@ -31,6 +34,7 @@ import com.tencent.polaris.api.rpc.GetAllInstancesRequest; import com.tencent.polaris.api.rpc.InstancesResponse; import com.tencent.polaris.api.utils.RuleUtils; +import com.tencent.polaris.api.utils.StringUtils; import com.tencent.polaris.client.api.BaseEngine; import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.client.pojo.Node; @@ -41,6 +45,9 @@ import com.tencent.polaris.metadata.core.TransitiveType; import com.tencent.polaris.metadata.core.manager.MetadataContext; import com.tencent.polaris.metadata.core.manager.MetadataContextHolder; +import com.tencent.polaris.plugins.connector.consul.service.common.TagConstant; +import com.tencent.polaris.plugins.connector.consul.service.router.RouterUtils; +import com.tencent.polaris.plugins.connector.consul.service.router.entity.RouteTag; import com.tencent.polaris.router.api.core.RouterAPI; import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse; @@ -57,7 +64,6 @@ import static com.tencent.polaris.metadata.core.constant.TsfMetadataConstants.TSF_APPLICATION_ID; import static com.tencent.polaris.metadata.core.constant.TsfMetadataConstants.TSF_GROUP_ID; import static com.tencent.polaris.metadata.core.constant.TsfMetadataConstants.TSF_NAMESPACE_ID; -import static com.tencent.polaris.metadata.core.constant.TsfMetadataConstants.TSF_SERVICE_TAG_OPERATOR; /** * RuleBasedRouter in TSF 集成测试 @@ -167,7 +173,7 @@ public void tearDown() { @Test public void testRouting1() throws InterruptedException { // 设置规则:NOT_IN + 多个服务名逗号分隔 + 不匹配(测试多个服务名,当前上游服务名在里面的情况) - setupSourceServiceNameRules(CALLER_SERVICE + ",mock-service", "*", ModelProto.MatchString.MatchStringType.NOT_IN); + setupSourceServiceNameRules(Arrays.asList(CALLER_SERVICE + ",mock-service"), "*", TagConstant.OPERATOR.NOT_IN); // 等待服务发现数据同步 Thread.sleep(3000); @@ -193,7 +199,7 @@ public void testRouting1() throws InterruptedException { @Test public void testRouting2() throws InterruptedException { // 设置规则:NOT_IN + 多个服务名 + 匹配(测试多个服务名,当前上游服务名不在里面的情况) - setupSourceServiceNameRules("mock-service,mock-service2", "*", ModelProto.MatchString.MatchStringType.NOT_IN); + setupSourceServiceNameRules(Arrays.asList("mock-service,mock-service2"), "*", TagConstant.OPERATOR.NOT_IN); // 等待服务发现数据同步 Thread.sleep(3000); @@ -222,7 +228,7 @@ public void testRouting2() throws InterruptedException { @Test public void testRouting3() throws InterruptedException { // 设置规则:EXACT + SOURCE_SERVICE_NAME + 匹配(精确匹配CallerService) - setupSourceServiceNameRules(CALLER_SERVICE, "*", ModelProto.MatchString.MatchStringType.EXACT); + setupSourceServiceNameRules(Arrays.asList(CALLER_SERVICE), "*", TagConstant.OPERATOR.EQUAL); // 等待服务发现数据同步 Thread.sleep(3000); @@ -251,7 +257,7 @@ public void testRouting3() throws InterruptedException { @Test public void testRouting4() throws InterruptedException { // 设置规则:REGEX + SOURCE_SERVICE_NAME + 匹配(正则表达式匹配CallerService) - setupSourceServiceNameRules("Caller.*", "*", ModelProto.MatchString.MatchStringType.REGEX); + setupSourceServiceNameRules(Arrays.asList("Caller.*"), "*", TagConstant.OPERATOR.REGEX); // 等待服务发现数据同步 Thread.sleep(3000); @@ -280,7 +286,7 @@ public void testRouting4() throws InterruptedException { @Test public void testRouting5() throws InterruptedException { // 设置规则:REGEX + SOURCE_SERVICE_NAME + 不匹配(正则表达式不匹配CallerService) - setupSourceServiceNameRules("MockService.*", "*", ModelProto.MatchString.MatchStringType.REGEX); + setupSourceServiceNameRules(Arrays.asList("MockService.*"), "*", TagConstant.OPERATOR.REGEX); // 等待服务发现数据同步 Thread.sleep(3000); @@ -306,7 +312,7 @@ public void testRouting5() throws InterruptedException { @Test public void testRouting6() throws InterruptedException { // 设置规则:IN + SOURCE_SERVICE_NAME + 匹配(在列表中匹配) - setupSourceServiceNameRules("CallerService,MockService", "*", ModelProto.MatchString.MatchStringType.IN); + setupSourceServiceNameRules(Arrays.asList("CallerService,MockService"), "*", TagConstant.OPERATOR.IN); // 等待服务发现数据同步 Thread.sleep(3000); @@ -335,7 +341,7 @@ public void testRouting6() throws InterruptedException { @Test public void testRouting7() throws InterruptedException { // 设置规则:IN + SOURCE_SERVICE_NAME + 不匹配(不在列表中) - setupSourceServiceNameRules("MockService1,MockService2", "*", ModelProto.MatchString.MatchStringType.IN); + setupSourceServiceNameRules(Arrays.asList("MockService1,MockService2"), "*", TagConstant.OPERATOR.IN); // 等待服务发现数据同步 Thread.sleep(3000); @@ -361,7 +367,7 @@ public void testRouting7() throws InterruptedException { @Test public void testRouting8() throws InterruptedException { // 设置规则:NOT_EQUALS + SOURCE_SERVICE_NAME + 匹配(不等于指定服务名) - setupSourceServiceNameRules("MockService", "*", ModelProto.MatchString.MatchStringType.NOT_EQUALS); + setupSourceServiceNameRules(Arrays.asList("MockService"), "*", TagConstant.OPERATOR.NOT_EQUAL); // 等待服务发现数据同步 Thread.sleep(3000); @@ -390,7 +396,7 @@ public void testRouting8() throws InterruptedException { @Test public void testRouting9() throws InterruptedException { // 设置规则:NOT_EQUALS + SOURCE_SERVICE_NAME + 不匹配(等于指定服务名) - setupSourceServiceNameRules(CALLER_SERVICE, "*", ModelProto.MatchString.MatchStringType.NOT_EQUALS); + setupSourceServiceNameRules(Arrays.asList(CALLER_SERVICE), "*", TagConstant.OPERATOR.NOT_EQUAL); // 等待服务发现数据同步 Thread.sleep(3000); @@ -416,7 +422,7 @@ public void testRouting9() throws InterruptedException { @Test public void testRouting10() throws InterruptedException { // 设置规则:空服务名 + 边界情况测试, 空服务名与 * 一样,满足 RuleUtils.isMatchAllValue - setupSourceServiceNameRules("", "*", ModelProto.MatchString.MatchStringType.EXACT); + setupSourceServiceNameRules(Arrays.asList(""), "*", TagConstant.OPERATOR.EQUAL); // 等待服务发现数据同步 Thread.sleep(3000); @@ -445,7 +451,7 @@ public void testRouting10() throws InterruptedException { @Test public void testRouting11() throws InterruptedException { // 设置规则:通配符 * + 全匹配(测试边界情况) - setupSourceServiceNameRules("*", "*", ModelProto.MatchString.MatchStringType.EXACT); + setupSourceServiceNameRules(Arrays.asList("*"), "*", TagConstant.OPERATOR.EQUAL); // 等待服务发现数据同步 Thread.sleep(3000); @@ -474,7 +480,7 @@ public void testRouting11() throws InterruptedException { @Test public void testRouting12() throws InterruptedException { // 设置规则:EXACT + 不同命名空间 + 不匹配(测试命名空间隔离) - setupSourceServiceNameRules(CALLER_SERVICE, "different-namespace", ModelProto.MatchString.MatchStringType.EXACT); + setupSourceServiceNameRules(Arrays.asList(CALLER_SERVICE), "different-namespace", TagConstant.OPERATOR.EQUAL); // 等待服务发现数据同步 Thread.sleep(3000); @@ -500,7 +506,7 @@ public void testRouting12() throws InterruptedException { @Test public void testRouting13() throws InterruptedException { // 设置规则:大小写敏感测试(EXACT匹配默认不区分大小写) - setupSourceServiceNameRules("callerservice", "*", ModelProto.MatchString.MatchStringType.EXACT); + setupSourceServiceNameRules(Arrays.asList("callerservice"), "*", TagConstant.OPERATOR.EQUAL); // 等待服务发现数据同步 Thread.sleep(3000); @@ -529,7 +535,7 @@ public void testRouting13() throws InterruptedException { @Test public void testRouting14() throws InterruptedException { // 设置规则:复杂正则表达式匹配(测试正则表达式的边界情况) - setupSourceServiceNameRules("^Caller.*Service$", "*", ModelProto.MatchString.MatchStringType.REGEX); + setupSourceServiceNameRules(Arrays.asList("^Caller.*Service$"), "*", TagConstant.OPERATOR.REGEX); // 等待服务发现数据同步 Thread.sleep(3000); @@ -581,13 +587,77 @@ public void testRouting15() throws InterruptedException { } } - private void setupSourceServiceNameRules(String sourceServiceName, String sourceNamespace, ModelProto.MatchString.MatchStringType matchType) { - RoutingProto.Source source = RoutingProto.Source.newBuilder() - .setService(StringValue.newBuilder().setValue(sourceServiceName)) - .setNamespace(StringValue.newBuilder().setValue(sourceNamespace)) - .putMetadata(TSF_SERVICE_TAG_OPERATOR, ModelProto.MatchString.newBuilder().setType(matchType).build()) - .putMetadata(RuleUtils.MATCH_ALL, ModelProto.MatchString.newBuilder().build()) - .build(); + @Test + public void testRouting16() throws InterruptedException { + // 设置规则:多个规则,都是服务名 EQUAL,无法满足 + setupSourceServiceNameRules(Arrays.asList(CALLER_SERVICE, "mock-service"), "*", TagConstant.OPERATOR.EQUAL); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果 + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(4, routedInstances.size()); + } + } + + @Test + public void testRouting17() throws InterruptedException { + // 设置规则:多个规则,服务名 NOT_EQUAL,满足 + setupSourceServiceNameRules(Arrays.asList("mock-service", "mock-service2"), "*", TagConstant.OPERATOR.NOT_EQUAL); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, null, null); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果 + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(2, routedInstances.size()); + + int[] ports = routedInstances.stream().mapToInt(Instance::getPort).sorted().toArray(); + Assert.assertArrayEquals(new int[] {8080, 8081}, ports); + } + } + + + + private void setupSourceServiceNameRules(List sourceServiceNames, String sourceNamespace, String operator) { + List routeTags = new ArrayList<>(); + for (String sourceServiceName : sourceServiceNames) { + RouteTag routeTag = new RouteTag(); + if (StringUtils.equals(sourceNamespace, RuleUtils.MATCH_ALL)) { + routeTag.setTagField(TagConstant.SYSTEM_FIELD.SOURCE_SERVICE_NAME); + routeTag.setTagValue(sourceServiceName); + } else { + routeTag.setTagField(TagConstant.SYSTEM_FIELD.SOURCE_NAMESPACE_SERVICE_NAME); + routeTag.setTagValue(sourceNamespace + "/" + sourceServiceName); + } + routeTag.setTagOperator(operator); + routeTags.add(routeTag); + } RoutingProto.Destination destination = RoutingProto.Destination.newBuilder() .setNamespace(StringValue.newBuilder().setValue(NAMESPACE).build()) @@ -599,8 +669,10 @@ private void setupSourceServiceNameRules(String sourceServiceName, String source RoutingProto.Routing routing = RoutingProto.Routing.newBuilder() .setService(StringValue.newBuilder().setValue(CALLEE_SERVICE).build()) .setNamespace(StringValue.newBuilder().setValue(NAMESPACE).build()) - .addInbounds(RoutingProto.Route.newBuilder().addDestinations(destination) - .addSources(source).build()) + .addInbounds(RoutingProto.Route.newBuilder() + .putMetadata(RouterConstants.MATCH_ALL_SOURCES, "true") + .addDestinations(destination) + .addAllSources(RouterUtils.parseTagListToSourceList(routeTags)).build()) .build(); namingServer.getNamingService().setRouting(calleeServiceKey, routing); From 83f66c708186ccd29065a297c59fe705fd185e75 Mon Sep 17 00:00:00 2001 From: shedfreewu Date: Thu, 5 Feb 2026 16:57:44 +0800 Subject: [PATCH 5/7] fix ut --- .../TsfRuleBasedRouterIntegrationTest.java | 162 +++++++++++++----- 1 file changed, 122 insertions(+), 40 deletions(-) diff --git a/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java b/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java index 49fa5b510..492f52b67 100644 --- a/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java +++ b/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java @@ -16,17 +16,23 @@ */ import java.io.IOException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; +import com.ecwid.consul.json.GsonFactory; +import com.ecwid.consul.transport.HttpResponse; +import com.ecwid.consul.v1.kv.model.GetValue; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.protobuf.StringValue; -import com.google.protobuf.UInt32Value; import com.tencent.polaris.api.config.Configuration; import com.tencent.polaris.api.core.ConsumerAPI; -import com.tencent.polaris.api.plugin.route.RouterConstants; import com.tencent.polaris.api.pojo.Instance; import com.tencent.polaris.api.pojo.ServiceInfo; import com.tencent.polaris.api.pojo.ServiceInstances; @@ -43,15 +49,21 @@ import com.tencent.polaris.metadata.core.MetadataContainer; import com.tencent.polaris.metadata.core.MetadataType; import com.tencent.polaris.metadata.core.TransitiveType; +import com.tencent.polaris.metadata.core.manager.CalleeMetadataContainerGroup; +import com.tencent.polaris.metadata.core.manager.MetadataContainerGroup; import com.tencent.polaris.metadata.core.manager.MetadataContext; import com.tencent.polaris.metadata.core.manager.MetadataContextHolder; import com.tencent.polaris.plugins.connector.consul.service.common.TagConstant; -import com.tencent.polaris.plugins.connector.consul.service.router.RouterUtils; +import com.tencent.polaris.plugins.connector.consul.service.router.RoutingService; +import com.tencent.polaris.plugins.connector.consul.service.router.entity.RouteDest; +import com.tencent.polaris.plugins.connector.consul.service.router.entity.RouteDestItem; +import com.tencent.polaris.plugins.connector.consul.service.router.entity.RouteRule; +import com.tencent.polaris.plugins.connector.consul.service.router.entity.RouteRuleGroup; import com.tencent.polaris.plugins.connector.consul.service.router.entity.RouteTag; +import com.tencent.polaris.plugins.router.metadata.MetadataRouter; import com.tencent.polaris.router.api.core.RouterAPI; import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse; -import com.tencent.polaris.specification.api.v1.model.ModelProto; import com.tencent.polaris.specification.api.v1.traffic.manage.RoutingProto; import com.tencent.polaris.test.common.TestUtils; import com.tencent.polaris.test.mock.discovery.NamingServer; @@ -60,6 +72,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.yaml.snakeyaml.Yaml; import static com.tencent.polaris.metadata.core.constant.TsfMetadataConstants.TSF_APPLICATION_ID; import static com.tencent.polaris.metadata.core.constant.TsfMetadataConstants.TSF_GROUP_ID; @@ -642,40 +655,108 @@ public void testRouting17() throws InterruptedException { } } - - private void setupSourceServiceNameRules(List sourceServiceNames, String sourceNamespace, String operator) { - List routeTags = new ArrayList<>(); - for (String sourceServiceName : sourceServiceNames) { - RouteTag routeTag = new RouteTag(); - if (StringUtils.equals(sourceNamespace, RuleUtils.MATCH_ALL)) { - routeTag.setTagField(TagConstant.SYSTEM_FIELD.SOURCE_SERVICE_NAME); - routeTag.setTagValue(sourceServiceName); - } else { - routeTag.setTagField(TagConstant.SYSTEM_FIELD.SOURCE_NAMESPACE_SERVICE_NAME); - routeTag.setTagValue(sourceNamespace + "/" + sourceServiceName); + try { + // 1. 创建 RouteRuleGroup 对象 + RouteRuleGroup routeRuleGroup = new RouteRuleGroup(); + routeRuleGroup.setRouteId("test-route-id"); + routeRuleGroup.setRouteName("test-route-name"); + routeRuleGroup.setMicroserviceName(CALLEE_SERVICE); + routeRuleGroup.setNamespaceId(NAMESPACE); + routeRuleGroup.setFallbackStatus(true); + routeRuleGroup.setMicroserviceId("test-microservice-id"); + + // 2. 创建 RouteRule 对象 + RouteRule routeRule = new RouteRule(); + routeRule.setRouteRuleId("test-route-rule-id"); + routeRule.setRouteId("test-route-id"); + + // 3. 创建 RouteTag 列表 + List routeTags = new ArrayList<>(); + for (String sourceServiceName : sourceServiceNames) { + RouteTag routeTag = new RouteTag(); + if (StringUtils.equals(sourceNamespace, RuleUtils.MATCH_ALL)) { + routeTag.setTagField(TagConstant.SYSTEM_FIELD.SOURCE_SERVICE_NAME); + routeTag.setTagValue(sourceServiceName); + } else { + routeTag.setTagField(TagConstant.SYSTEM_FIELD.SOURCE_NAMESPACE_SERVICE_NAME); + routeTag.setTagValue(sourceNamespace + "/" + sourceServiceName); + } + routeTag.setRouteRuleId("test-route-rule-id"); + routeTag.setTagOperator(operator); + routeTag.setTagId(UUID.randomUUID().toString()); + routeTag.setTagType(TagConstant.TYPE.SYSTEM); + routeTags.add(routeTag); } - routeTag.setTagOperator(operator); - routeTags.add(routeTag); + routeRule.setTagList(routeTags); + + // 4. 创建 RouteDest 对象 + RouteDest routeDest = new RouteDest(); + routeDest.setDestId("test-dest-id"); + routeDest.setDestWeight(100); + routeDest.setRouteRuleId("test-route-rule-id"); + + // 5. 创建 RouteDestItem 列表 + List destItems = new ArrayList<>(); + RouteDestItem groupItem = new RouteDestItem(); + groupItem.setDestItemField(TSF_GROUP_ID); + groupItem.setDestItemValue(ruleCalleeGroupId); + groupItem.setRouteDestId("test-dest-id"); + groupItem.setRouteDestItemId("test-dest-item-id"); + destItems.add(groupItem); + routeDest.setDestItemList(destItems); + + // 6. 设置 RouteRule 的 destList + List destList = new ArrayList<>(); + destList.add(routeDest); + routeRule.setDestList(destList); + + // 7. 设置 RouteRuleGroup 的 ruleList + List ruleList = new ArrayList<>(); + ruleList.add(routeRule); + routeRuleGroup.setRuleList(ruleList); + + // 8. 将 RouteRuleGroup 转成 YAML 格式 + Yaml yaml = new Yaml(); + String yamlContent = yaml.dump(Arrays.asList(routeRuleGroup)); + + // 9. 将 YAML 转成 base64 + String base64Content = Base64.getEncoder().encodeToString(yamlContent.getBytes()); + + // 10. 创建 GetValue 对象并设置 base64 内容 + GetValue getValue = new GetValue(); + getValue.setValue(base64Content); + + // 11. 创建 GetValue 列表并转成 JSON + List getValueList = Arrays.asList(getValue); + String jsonContent = GsonFactory.getGson().toJson(getValueList); + + // 12. 创建模拟的 HttpResponse + HttpResponse response = new HttpResponse(200, "OK", jsonContent, null, false, 0L); + + // 11. 通过反射调用 parseResponse 方法 + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + RoutingService routingService = new RoutingService(null, null, null, "test", mapper); + Method parseResponseMethod = RoutingService.class.getDeclaredMethod("parseResponse", HttpResponse.class, String.class, String.class); + parseResponseMethod.setAccessible(true); + + @SuppressWarnings("unchecked") + List routes = (List) parseResponseMethod.invoke(routingService, response, NAMESPACE, CALLEE_SERVICE); + + // 12. 构建路由规则 + RoutingProto.Routing routing = RoutingProto.Routing.newBuilder() + .setService(StringValue.newBuilder().setValue(CALLEE_SERVICE).build()) + .setNamespace(StringValue.newBuilder().setValue(NAMESPACE).build()) + .addAllInbounds(routes) + .build(); + + namingServer.getNamingService().setRouting(calleeServiceKey, routing); + + } catch (Exception e) { + throw new RuntimeException("Failed to setup source service name rules", e); } - - RoutingProto.Destination destination = RoutingProto.Destination.newBuilder() - .setNamespace(StringValue.newBuilder().setValue(NAMESPACE).build()) - .setService(StringValue.newBuilder().setValue(CALLEE_SERVICE).build()) - .putMetadata(TSF_GROUP_ID, ModelProto.MatchString.newBuilder().setValue(StringValue.newBuilder().setValue(ruleCalleeGroupId)).build()) - .setWeight(UInt32Value.newBuilder().setValue(100).build()) - .build(); - - RoutingProto.Routing routing = RoutingProto.Routing.newBuilder() - .setService(StringValue.newBuilder().setValue(CALLEE_SERVICE).build()) - .setNamespace(StringValue.newBuilder().setValue(NAMESPACE).build()) - .addInbounds(RoutingProto.Route.newBuilder() - .putMetadata(RouterConstants.MATCH_ALL_SOURCES, "true") - .addDestinations(destination) - .addAllSources(RouterUtils.parseTagListToSourceList(routeTags)).build()) - .build(); - - namingServer.getNamingService().setRouting(calleeServiceKey, routing); } // ==================== 辅助方法 ==================== @@ -694,9 +775,6 @@ private ServiceInstances getAllInstances(String serviceName) { /** * 构建路由请求 * 模拟 caller(主调服务)调用 callee(被调服务) - * - * Header 需要设置在 MetadataContext 的 MessageMetadataContainer 中, - * 而不是通过 addRouterMetadata 方法 */ private ProcessRoutersRequest buildRouterRequest(ServiceInstances serviceInstances, String key, @@ -717,14 +795,18 @@ private ProcessRoutersRequest buildRouterRequest(ServiceInstances serviceInstanc // 设置目标实例(callee 的实例) request.setDstInstances(serviceInstances); - // 通过 MetadataContextHolder 设置 header 到 MetadataContainer - // LaneRouter 会从 MetadataContext 的 caller/callee MetadataContainer 中获取 key + MetadataContainerGroup metadataContainerGroup = new CalleeMetadataContainerGroup( + "X-Polaris-Metadata-Transitive-"); + request.setMetadataContainerGroup(metadataContainerGroup); if (key != null && value != null) { MetadataContext metadataContext = MetadataContextHolder.getOrCreate(); // 设置到 caller(主调方)的 MetadataContainer MetadataContainer calleeMessageContainer = metadataContext.getMetadataContainer(MetadataType.CUSTOM, false); calleeMessageContainer.putMetadataStringValue(key, value, TransitiveType.PASS_THROUGH); + + MetadataContainer metadataContainer = metadataContainerGroup.getCustomMetadataContainer(); + metadataContainer.putMetadataStringValue(key, value, TransitiveType.PASS_THROUGH); } return request; From f328ca6600e0abf6a60cf66634f8c7d46d39488d Mon Sep 17 00:00:00 2001 From: shedfreewu Date: Thu, 5 Feb 2026 17:52:48 +0800 Subject: [PATCH 6/7] spec version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5cf4a6d76..469d22778 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ 2.1.1.0 - 1.7.0 + 1.8.0-SNAPSHOT ${maven.build.timestamp} false yyyy-MM-dd HH:mm From 0298e700c8f859d2985601bb630c582eea1b02e3 Mon Sep 17 00:00:00 2001 From: shedfreewu Date: Thu, 5 Feb 2026 19:28:10 +0800 Subject: [PATCH 7/7] add --- .../fault/client/flow/DefaultFaultFlow.java | 7 +- .../api/plugin/route/RouterConstants.java | 4 +- .../consul/service/fault/FaultService.java | 2 +- .../service/mirroring/MirroringService.java | 2 +- .../consul/service/router/RoutingService.java | 2 +- .../mirroring/TrafficMirroringRouter.java | 7 +- .../plugins/router/rule/RuleBasedRouter.java | 8 +- .../TsfRuleBasedRouterIntegrationTest.java | 132 +++++++++++++++++- 8 files changed, 152 insertions(+), 12 deletions(-) diff --git a/polaris-fault/polaris-fault-client/src/main/java/com/tencent/polaris/fault/client/flow/DefaultFaultFlow.java b/polaris-fault/polaris-fault-client/src/main/java/com/tencent/polaris/fault/client/flow/DefaultFaultFlow.java index 37b670d2c..077dedd4c 100644 --- a/polaris-fault/polaris-fault-client/src/main/java/com/tencent/polaris/fault/client/flow/DefaultFaultFlow.java +++ b/polaris-fault/polaris-fault-client/src/main/java/com/tencent/polaris/fault/client/flow/DefaultFaultFlow.java @@ -165,19 +165,22 @@ private boolean matchSource(FaultInjectionProto.FaultInjection faultInjection, S if (CollectionUtils.isEmpty(sources)) { return true; } - boolean matchAllSources = faultInjection.containsMetadata(RouterConstants.MATCH_ALL_SOURCES); + boolean tsfSourcesMatchMode = faultInjection.containsMetadata(RouterConstants.TSF_SOURCES_MATCH_MODE); // source匹配成功标志 boolean matched = true; for (RoutingProto.Source source : sources) { // 匹配source服务 matched = RoutingUtils.matchSourceService(source, sourceService); if (!matched) { + if (tsfSourcesMatchMode) { + break; + } continue; } // 匹配source metadata matched = RoutingUtils.matchSourceMetadata(source, sourceService, metadataContainerGroup, key -> getFlowCache().loadPluginCacheObject(API_ID, key, path -> TrieUtil.buildSimpleApiTrieNode((String) path))); - if (matchAllSources) { + if (tsfSourcesMatchMode) { if (!matched) { return false; } diff --git a/polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/route/RouterConstants.java b/polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/route/RouterConstants.java index 9ade5b58e..9c165edc7 100644 --- a/polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/route/RouterConstants.java +++ b/polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/route/RouterConstants.java @@ -19,8 +19,8 @@ public interface RouterConstants { - // match all sources - String MATCH_ALL_SOURCES = "match-all-sources"; + // TSF sources match mode + String TSF_SOURCES_MATCH_MODE = "tsf-sources-match-mode"; //set路由 String SET_ENABLE_KEY = "internal-enable-set"; diff --git a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/fault/FaultService.java b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/fault/FaultService.java index c627328a2..f8843f093 100644 --- a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/fault/FaultService.java +++ b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/fault/FaultService.java @@ -197,7 +197,7 @@ private List parseResponse(final HttpRespons continue; } FaultInjectionProto.FaultInjection.Builder faultInjectionBuilder = FaultInjectionProto.FaultInjection.newBuilder(); - faultInjectionBuilder.putMetadata(RouterConstants.MATCH_ALL_SOURCES, "true"); + faultInjectionBuilder.putMetadata(RouterConstants.TSF_SOURCES_MATCH_MODE, "true"); // parse sources List sources = RouterUtils.parseTagListToSourceList(routeRule.getTagList()); faultInjectionBuilder.addAllSources(sources); diff --git a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/mirroring/MirroringService.java b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/mirroring/MirroringService.java index 12b1433bf..fa4455b5f 100644 --- a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/mirroring/MirroringService.java +++ b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/mirroring/MirroringService.java @@ -203,7 +203,7 @@ private List parseResponse(final HttpRes // parse enabled trafficMirroringBuilder.setEnabled(BoolValue.of(mirrorRule.getEnabled())); - trafficMirroringBuilder.putMetadata(RouterConstants.MATCH_ALL_SOURCES, "true"); + trafficMirroringBuilder.putMetadata(RouterConstants.TSF_SOURCES_MATCH_MODE, "true"); // parse sources List sources = RouterUtils.parseTagListToSourceList(routeRule.getTagList()); diff --git a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RoutingService.java b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RoutingService.java index 6dccb3538..9e723b35c 100644 --- a/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RoutingService.java +++ b/polaris-plugins/polaris-plugins-connector/connector-consul/src/main/java/com/tencent/polaris/plugins/connector/consul/service/router/RoutingService.java @@ -204,7 +204,7 @@ private List parseResponse(final HttpResponse response, Stri } RoutingProto.Route.Builder routeBuilder = RoutingProto.Route.newBuilder(); routeBuilder.putMetadata(ROUTER_FAULT_TOLERANCE_ENABLE, String.valueOf(routeRuleGroup.getFallbackStatus())); - routeBuilder.putMetadata(RouterConstants.MATCH_ALL_SOURCES, "true"); + routeBuilder.putMetadata(RouterConstants.TSF_SOURCES_MATCH_MODE, "true"); // parse sources List sources = RouterUtils.parseTagListToSourceList(routeRule.getTagList()); diff --git a/polaris-plugins/polaris-plugins-router/router-mirroring/src/main/java/com/tencent/polaris/plugins/router/mirroring/TrafficMirroringRouter.java b/polaris-plugins/polaris-plugins-router/router-mirroring/src/main/java/com/tencent/polaris/plugins/router/mirroring/TrafficMirroringRouter.java index f481a605a..5d255c5d9 100644 --- a/polaris-plugins/polaris-plugins-router/router-mirroring/src/main/java/com/tencent/polaris/plugins/router/mirroring/TrafficMirroringRouter.java +++ b/polaris-plugins/polaris-plugins-router/router-mirroring/src/main/java/com/tencent/polaris/plugins/router/mirroring/TrafficMirroringRouter.java @@ -127,19 +127,22 @@ private boolean matchSource(TrafficMirroringProto.TrafficMirroring trafficMirror if (CollectionUtils.isEmpty(sources)) { return true; } - boolean matchAllSources = trafficMirroring.containsMetadata(RouterConstants.MATCH_ALL_SOURCES); + boolean tsfSourcesMatchMode = trafficMirroring.containsMetadata(RouterConstants.TSF_SOURCES_MATCH_MODE); // source匹配成功标志 boolean matched = true; for (RoutingProto.Source source : sources) { // 匹配source服务 matched = RoutingUtils.matchSourceService(source, sourceService); if (!matched) { + if (tsfSourcesMatchMode) { + break; + } continue; } // 匹配source metadata matched = RoutingUtils.matchSourceMetadata(source, sourceService, metadataContainerGroup, key -> flowCache.loadPluginCacheObject(API_ID, key, path -> TrieUtil.buildSimpleApiTrieNode((String) path))); - if (matchAllSources) { + if (tsfSourcesMatchMode) { if (!matched) { return false; } diff --git a/polaris-plugins/polaris-plugins-router/router-rule/src/main/java/com/tencent/polaris/plugins/router/rule/RuleBasedRouter.java b/polaris-plugins/polaris-plugins-router/router-rule/src/main/java/com/tencent/polaris/plugins/router/rule/RuleBasedRouter.java index bf5334302..15b76f9fb 100644 --- a/polaris-plugins/polaris-plugins-router/router-rule/src/main/java/com/tencent/polaris/plugins/router/rule/RuleBasedRouter.java +++ b/polaris-plugins/polaris-plugins-router/router-rule/src/main/java/com/tencent/polaris/plugins/router/rule/RuleBasedRouter.java @@ -117,7 +117,8 @@ private boolean matchSource(RoutingProto.Route route, Service sourceService, if (CollectionUtils.isEmpty(sources)) { return true; } - boolean matchAllSources = route.containsMetadata(RouterConstants.MATCH_ALL_SOURCES); + // tsf mode, need match all sources, include service. + boolean tsfSourcesMatchMode = route.containsMetadata(RouterConstants.TSF_SOURCES_MATCH_MODE); // source匹配成功标志 boolean matched = true; @@ -126,6 +127,9 @@ private boolean matchSource(RoutingProto.Route route, Service sourceService, if (ruleMatchType == RuleMatchType.destRouteRuleMatch) { matched = RoutingUtils.matchSourceService(source, sourceService); if (!matched) { + if (tsfSourcesMatchMode) { + break; + } continue; } } @@ -133,7 +137,7 @@ private boolean matchSource(RoutingProto.Route route, Service sourceService, matched = RoutingUtils.matchSourceMetadata(source, sourceService, trafficLabels, metadataContainerGroup, multiEnvRouterParamMap, globalVariablesConfig, key -> flowCache.loadPluginCacheObject(API_ID, key, path -> TrieUtil.buildSimpleApiTrieNode((String) path))); - if (matchAllSources) { + if (tsfSourcesMatchMode) { if (!matched) { return false; } diff --git a/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java b/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java index 492f52b67..26055cf2b 100644 --- a/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java +++ b/polaris-router/polaris-router-factory/src/test/java/TsfRuleBasedRouterIntegrationTest.java @@ -60,7 +60,6 @@ import com.tencent.polaris.plugins.connector.consul.service.router.entity.RouteRule; import com.tencent.polaris.plugins.connector.consul.service.router.entity.RouteRuleGroup; import com.tencent.polaris.plugins.connector.consul.service.router.entity.RouteTag; -import com.tencent.polaris.plugins.router.metadata.MetadataRouter; import com.tencent.polaris.router.api.core.RouterAPI; import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse; @@ -655,7 +654,126 @@ public void testRouting17() throws InterruptedException { } } + @Test + public void testRouting18() throws InterruptedException { + // 设置规则:多个规则,服务名 + 自定义标签 EQUAL,满足 + Map userTags = new HashMap<>(); + userTags.put("k1", "v1"); + setupSourceServiceNameRules(Arrays.asList(CALLER_SERVICE), "*", TagConstant.OPERATOR.EQUAL, userTags); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, "k1", "v1"); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果 + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(2, routedInstances.size()); + + int[] ports = routedInstances.stream().mapToInt(Instance::getPort).sorted().toArray(); + Assert.assertArrayEquals(new int[] {8080, 8081}, ports); + } + } + + @Test + public void testRouting19() throws InterruptedException { + // 设置规则:多个规则,服务名 + 自定义标签 EQUAL,自定义标签不满足 + Map userTags = new HashMap<>(); + userTags.put("k1", "v1"); + setupSourceServiceNameRules(Arrays.asList(CALLER_SERVICE), "*", TagConstant.OPERATOR.EQUAL, userTags); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, "k1", "v2"); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果 + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(4, routedInstances.size()); + } + } + + @Test + public void testRouting20() throws InterruptedException { + // 设置规则:多个规则,服务名 + 自定义标签 EQUAL,服务名不满足 + Map userTags = new HashMap<>(); + userTags.put("k1", "v1"); + setupSourceServiceNameRules(Arrays.asList("mock-service"), "*", TagConstant.OPERATOR.EQUAL, userTags); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, "k1", "v1"); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果 + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(4, routedInstances.size()); + } + } + + @Test + public void testRouting21() throws InterruptedException { + // 设置规则:多个规则,服务名 + 自定义标签 EQUAL,均不满足 + Map userTags = new HashMap<>(); + userTags.put("k1", "v1"); + setupSourceServiceNameRules(Arrays.asList("mock-service"), "*", TagConstant.OPERATOR.EQUAL, userTags); + + // 等待服务发现数据同步 + Thread.sleep(3000); + + // 获取 callee 服务的所有实例 + ServiceInstances serviceInstances = getAllInstances(CALLEE_SERVICE); + Assert.assertNotNull(serviceInstances); + Assert.assertEquals(4, serviceInstances.getInstances().size()); + + for (int i = 0; i < 10; i++) { + ProcessRoutersRequest request = buildRouterRequest(serviceInstances, "k1", "v2"); + + // 执行路由 + ProcessRoutersResponse response = routerAPI.processRouters(request); + + // 验证结果 + List routedInstances = response.getServiceInstances().getInstances(); + Assert.assertNotNull(routedInstances); + Assert.assertEquals(4, routedInstances.size()); + } + } + private void setupSourceServiceNameRules(List sourceServiceNames, String sourceNamespace, String operator) { + setupSourceServiceNameRules(sourceServiceNames, sourceNamespace, operator, new HashMap<>()); + } + + private void setupSourceServiceNameRules(List sourceServiceNames, String sourceNamespace, String operator, Map userTags) { try { // 1. 创建 RouteRuleGroup 对象 RouteRuleGroup routeRuleGroup = new RouteRuleGroup(); @@ -688,6 +806,18 @@ private void setupSourceServiceNameRules(List sourceServiceNames, String routeTag.setTagType(TagConstant.TYPE.SYSTEM); routeTags.add(routeTag); } + + for (Map.Entry entry : userTags.entrySet()) { + RouteTag routeTag = new RouteTag(); + routeTag.setTagField(entry.getKey()); + routeTag.setTagValue(entry.getValue()); + routeTag.setRouteRuleId("test-route-rule-id"); + routeTag.setTagOperator(operator); + routeTag.setTagId(UUID.randomUUID().toString()); + routeTag.setTagType(TagConstant.TYPE.CUSTOM); + routeTags.add(routeTag); + } + routeRule.setTagList(routeTags); // 4. 创建 RouteDest 对象