This is an automated email from the ASF dual-hosted git repository.

heliang666s pushed a commit to branch feat-tagStateRouter
in repository https://gitbox.apache.org/repos/asf/dubbo.git

commit a75591c6c0f9d60097090a7d321460b689c3a04a
Author: 何亮 <[email protected]>
AuthorDate: Tue Dec 16 02:20:34 2025 +0800

    fix(cluster): support broadcast and wildcard tag routing
---
 .../rpc/cluster/router/tag/TagStateRouter.java     |  21 +++-
 .../rpc/cluster/router/tag/TagStateRouterTest.java | 114 +++++++++++++++++++++
 2 files changed, 131 insertions(+), 4 deletions(-)

diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java
index cacb941b16..07c3186625 100644
--- 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java
@@ -41,6 +41,9 @@ import java.util.Set;
 import java.util.function.Predicate;
 
 import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
+import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
+import static 
org.apache.dubbo.common.constants.CommonConstants.BROADCAST_CLUSTER;
+import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TAG_KEY;
 import static 
org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_EMPTY;
 import static 
org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_INVALID;
@@ -105,6 +108,17 @@ public class TagStateRouter<T> extends 
AbstractStateRouter<T> implements Configu
             return invokers;
         }
 
+        String tag = StringUtils.isEmpty(invocation.getAttachment(TAG_KEY))
+                ? url.getParameter(TAG_KEY)
+                : invocation.getAttachment(TAG_KEY);
+        if (ANY_VALUE.equals(tag)
+                || (StringUtils.isEmpty(tag) && 
BROADCAST_CLUSTER.equals(url.getParameter(CLUSTER_KEY)))) {
+            if (needToPrintMessage) {
+                messageHolder.set("Skip tag routing. Reason: wildcard tag 
request or broadcast cluster without tag");
+            }
+            return invokers;
+        }
+
         // since the rule can be changed by config center, we should copy one 
to use.
         final TagRouterRule tagRouterRuleCopy = tagRouterRule;
         if (tagRouterRuleCopy == null || !tagRouterRuleCopy.isValid() || 
!tagRouterRuleCopy.isEnabled()) {
@@ -115,9 +129,6 @@ public class TagStateRouter<T> extends 
AbstractStateRouter<T> implements Configu
         }
 
         BitList<Invoker<T>> result = invokers;
-        String tag = StringUtils.isEmpty(invocation.getAttachment(TAG_KEY))
-                ? url.getParameter(TAG_KEY)
-                : invocation.getAttachment(TAG_KEY);
 
         // if we are requesting for a Provider with a specific tag
         if (StringUtils.isNotEmpty(tag)) {
@@ -208,7 +219,9 @@ public class TagStateRouter<T> extends 
AbstractStateRouter<T> implements Configu
         // Tag request
         if (!StringUtils.isEmpty(tag)) {
             result = filterInvoker(
-                    invokers, invoker -> 
tag.equals(invoker.getUrl().getParameter(TAG_KEY)));
+                    invokers,
+                    invoker ->
+                            ANY_VALUE.equals(tag) || 
tag.equals(invoker.getUrl().getParameter(TAG_KEY)));
             if (CollectionUtils.isEmpty(result) && !isForceUseTag(invocation)) 
{
                 result = filterInvoker(
                         invokers,
diff --git 
a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java
 
b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java
index 79b34c6823..d228a315e7 100644
--- 
a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java
+++ 
b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java
@@ -41,6 +41,9 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
+import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
+import static 
org.apache.dubbo.common.constants.CommonConstants.BROADCAST_CLUSTER;
+import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TAG_KEY;
 import static org.mockito.Mockito.when;
 
@@ -298,4 +301,115 @@ class TagStateRouterTest {
         Set<String> selectedAddresses = 
TagStateRouter.selectAddressByTagLevel(tagAddresses, "beta", true);
         Assertions.assertEquals(addresses, selectedAddresses);
     }
+
+    @Test
+    void testTagRouteWithWildcardShouldReturnAllProviders() {
+        StateRouter<TagRouterRule> router = new 
TagStateRouterFactory().getRouter(TagRouterRule.class, url);
+
+        List<Invoker<TagRouterRule>> originInvokers = new ArrayList<>();
+
+        URL url1 = 
URL.valueOf("test://127.0.0.1:7777/DemoInterface?dubbo.tag=t-01")
+                .setScopeModel(moduleModel);
+        URL url2 = 
URL.valueOf("test://127.0.0.1:7778/DemoInterface?dubbo.tag=t-02")
+                .setScopeModel(moduleModel);
+        URL url3 = 
URL.valueOf("test://127.0.0.1:7779/DemoInterface?dubbo.tag=t-03")
+                .setScopeModel(moduleModel);
+        URL url4 = 
URL.valueOf("test://127.0.0.1:7780/DemoInterface?dubbo.tag=t-04")
+                .setScopeModel(moduleModel);
+
+        Invoker<TagRouterRule> invoker1 = new MockInvoker<>(url1, true);
+        Invoker<TagRouterRule> invoker2 = new MockInvoker<>(url2, true);
+        Invoker<TagRouterRule> invoker3 = new MockInvoker<>(url3, true);
+        Invoker<TagRouterRule> invoker4 = new MockInvoker<>(url4, true);
+        originInvokers.add(invoker1);
+        originInvokers.add(invoker2);
+        originInvokers.add(invoker3);
+        originInvokers.add(invoker4);
+        BitList<Invoker<TagRouterRule>> invokers = new 
BitList<>(originInvokers);
+
+        RpcInvocation invocation = new RpcInvocation();
+        invocation.setAttachment(TAG_KEY, ANY_VALUE);
+
+        List<Invoker<TagRouterRule>> filteredInvokers =
+                router.route(invokers.clone(), invokers.get(0).getUrl(), 
invocation, false, new Holder<>());
+
+        Assertions.assertEquals(4, filteredInvokers.size());
+        Assertions.assertEquals(invoker1, filteredInvokers.get(0));
+        Assertions.assertEquals(invoker2, filteredInvokers.get(1));
+        Assertions.assertEquals(invoker3, filteredInvokers.get(2));
+        Assertions.assertEquals(invoker4, filteredInvokers.get(3));
+    }
+
+    @Test
+    void testBroadcastClusterWithoutTagShouldReturnAllProviders() {
+        StateRouter<TagRouterRule> router = new 
TagStateRouterFactory().getRouter(TagRouterRule.class, url);
+
+        List<Invoker<TagRouterRule>> originInvokers = new ArrayList<>();
+
+        URL url1 = 
URL.valueOf("test://127.0.0.1:7777/DemoInterface?dubbo.tag=t-01")
+                .setScopeModel(moduleModel);
+        URL url2 = 
URL.valueOf("test://127.0.0.1:7778/DemoInterface?dubbo.tag=t-02")
+                .setScopeModel(moduleModel);
+        URL url3 = 
URL.valueOf("test://127.0.0.1:7779/DemoInterface?dubbo.tag=t-03")
+                .setScopeModel(moduleModel);
+        URL url4 = 
URL.valueOf("test://127.0.0.1:7780/DemoInterface?dubbo.tag=t-04")
+                .setScopeModel(moduleModel);
+
+        Invoker<TagRouterRule> invoker1 = new MockInvoker<>(url1, true);
+        Invoker<TagRouterRule> invoker2 = new MockInvoker<>(url2, true);
+        Invoker<TagRouterRule> invoker3 = new MockInvoker<>(url3, true);
+        Invoker<TagRouterRule> invoker4 = new MockInvoker<>(url4, true);
+        originInvokers.add(invoker1);
+        originInvokers.add(invoker2);
+        originInvokers.add(invoker3);
+        originInvokers.add(invoker4);
+        BitList<Invoker<TagRouterRule>> invokers = new 
BitList<>(originInvokers);
+
+        RpcInvocation invocation = new RpcInvocation();
+        URL consumerUrl = url.addParameter(CLUSTER_KEY, BROADCAST_CLUSTER);
+
+        List<Invoker<TagRouterRule>> filteredInvokers =
+                router.route(invokers.clone(), consumerUrl, invocation, false, 
new Holder<>());
+
+        Assertions.assertEquals(4, filteredInvokers.size());
+        Assertions.assertEquals(invoker1, filteredInvokers.get(0));
+        Assertions.assertEquals(invoker2, filteredInvokers.get(1));
+        Assertions.assertEquals(invoker3, filteredInvokers.get(2));
+        Assertions.assertEquals(invoker4, filteredInvokers.get(3));
+    }
+
+    @Test
+    void testTagRouteWithDynamicRuleWildcardShouldReturnAllProviders() {
+        TagStateRouter router = (TagStateRouter) new 
TagStateRouterFactory().getRouter(TagRouterRule.class, url);
+        router = Mockito.spy(router);
+
+        List<Invoker<String>> originInvokers = new ArrayList<>();
+
+        URL url1 = 
URL.valueOf("test://127.0.0.1:7777/DemoInterface?application=foo&dubbo.tag=tag2&match_key=value")
+                .setScopeModel(moduleModel);
+        URL url2 = 
URL.valueOf("test://127.0.0.1:7778/DemoInterface?application=foo&match_key=value")
+                .setScopeModel(moduleModel);
+        URL url3 = 
URL.valueOf("test://127.0.0.1:7779/DemoInterface?application=foo")
+                .setScopeModel(moduleModel);
+        Invoker<String> invoker1 = new MockInvoker<>(url1, true);
+        Invoker<String> invoker2 = new MockInvoker<>(url2, true);
+        Invoker<String> invoker3 = new MockInvoker<>(url3, true);
+        originInvokers.add(invoker1);
+        originInvokers.add(invoker2);
+        originInvokers.add(invoker3);
+        BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
+
+        RpcInvocation invocation = new RpcInvocation();
+        invocation.setAttachment(TAG_KEY, ANY_VALUE);
+        TagRouterRule rule = getTagRule();
+        Mockito.when(router.getInvokers()).thenReturn(invokers);
+        rule.init(router);
+        router.setTagRouterRule(rule);
+        List<Invoker<String>> filteredInvokers = 
router.route(invokers.clone(), url, invocation, false, new Holder<>());
+
+        Assertions.assertEquals(3, filteredInvokers.size());
+        Assertions.assertEquals(invoker1, filteredInvokers.get(0));
+        Assertions.assertEquals(invoker2, filteredInvokers.get(1));
+        Assertions.assertEquals(invoker3, filteredInvokers.get(2));
+    }
 }

Reply via email to