diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java index 31372ca8a37..34fa8b5403b 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java @@ -856,6 +856,23 @@ public static String join(Collection coll, String split) { return sb.toString(); } + public static String join(final Object[] array, final char delimiter, final int startIndex, final int endIndex) { + if (ArrayUtils.isEmpty(array)) { + return EMPTY_STRING; + } + if (endIndex - startIndex <= 0) { + return EMPTY_STRING; + } + StringBuilder sb = new StringBuilder(); + for (int i = startIndex; i < endIndex; i++) { + if (i > 0) { + sb.append(delimiter); + } + sb.append(array[i]); + } + return sb.toString(); + } + /** * parse key-value pair. * diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java index 5d46c2ad18b..2ac7ccc411c 100644 --- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java @@ -227,6 +227,10 @@ void testJoin() throws Exception { assertEquals(StringUtils.join(s), "123"); assertEquals(StringUtils.join(s, ','), "1,2,3"); assertEquals(StringUtils.join(s, ","), "1,2,3"); + assertEquals(StringUtils.join(s, ',', 0, 1), "1"); + assertEquals(StringUtils.join(s, ',', 0, 2), "1,2"); + assertEquals(StringUtils.join(s, ',', 0, 3), "1,2,3"); + assertEquals("", StringUtils.join(s, ',', 2, 0), "1,2"); } @Test @@ -502,4 +506,4 @@ void testStartsWithIgnoreCase() { assertTrue(startsWithIgnoreCase("Dubbo.application.name", "dubbo.application.")); } -} \ No newline at end of file +} diff --git a/dubbo-plugin/dubbo-plugin-router-tag/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java b/dubbo-plugin/dubbo-plugin-router-tag/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java index c3470776b25..f6e7d9dc334 100644 --- a/dubbo-plugin/dubbo-plugin-router-tag/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java +++ b/dubbo-plugin/dubbo-plugin-router-tag/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java @@ -36,6 +36,7 @@ import org.apache.dubbo.rpc.cluster.router.tag.model.TagRouterRule; import org.apache.dubbo.rpc.cluster.router.tag.model.TagRuleParser; +import java.util.Map; import java.util.Set; import java.util.function.Predicate; @@ -52,6 +53,7 @@ public class TagStateRouter extends AbstractStateRouter implements Configu public static final String NAME = "TAG_ROUTER"; private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(TagStateRouter.class); private static final String RULE_SUFFIX = ".tag-router"; + public static final char TAG_SEPERATOR = '|'; private volatile TagRouterRule tagRouterRule; private String application; @@ -106,7 +108,8 @@ public BitList> doRoute(BitList> invokers, URL url, Invoca // if we are requesting for a Provider with a specific tag if (StringUtils.isNotEmpty(tag)) { - Set addresses = tagRouterRuleCopy.getTagnameToAddresses().get(tag); + Map> tagnameToAddresses = tagRouterRuleCopy.getTagnameToAddresses(); + Set addresses = selectAddressByTagLevel(tagnameToAddresses, tag, isForceUseTag(invocation)); // filter by dynamic tag group first if (addresses != null) { // null means tag not set result = filterInvoker(invokers, invoker -> addressMatches(invoker.getUrl(), addresses)); @@ -306,4 +309,34 @@ public void stop() { public void setTagRouterRule(TagRouterRule tagRouterRule) { this.tagRouterRule = tagRouterRule; } + + /** + * select addresses by tag with level + *

+ * example: + * selector=beta|team1|partner1 + * step1.select tagAddresses with selector=beta|team1|partner1, if result is empty, then run step2 + * step2.select tagAddresses with selector=beta|team1, if result is empty, then run step3 + * step3.select tagAddresses with selector=beta, if result is empty, result is null + *

+ * + * @param tagAddresses + * @param tagSelector eg: beta|team1|partner1 + * @return + */ + public static Set selectAddressByTagLevel(Map> tagAddresses, String tagSelector, boolean isForce) { + if (isForce || StringUtils.isNotContains(tagSelector, TAG_SEPERATOR)) { + return tagAddresses.get(tagSelector); + } + String[] selectors = StringUtils.split(tagSelector, TAG_SEPERATOR); + for (int i = selectors.length; i > 0; i--) { + String selectorTmp = StringUtils.join(selectors, TAG_SEPERATOR, 0, i); + Set addresses = tagAddresses.get(selectorTmp); + if (CollectionUtils.isNotEmpty(addresses)) { + return addresses; + } + } + return null; + } + } diff --git a/dubbo-plugin/dubbo-plugin-router-tag/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java b/dubbo-plugin/dubbo-plugin-router-tag/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java index 228a667eef8..6477c09b6c3 100644 --- a/dubbo-plugin/dubbo-plugin-router-tag/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java +++ b/dubbo-plugin/dubbo-plugin-router-tag/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java @@ -24,7 +24,6 @@ import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcInvocation; import org.apache.dubbo.rpc.cluster.router.MockInvoker; -//import org.apache.dubbo.rpc.cluster.router.mesh.util.TracingContextProvider; import org.apache.dubbo.rpc.cluster.router.state.BitList; import org.apache.dubbo.rpc.cluster.router.state.StateRouter; import org.apache.dubbo.rpc.cluster.router.tag.model.TagRouterRule; @@ -32,13 +31,17 @@ import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.rpc.model.ModuleModel; +import com.google.common.collect.Sets; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import static org.apache.dubbo.common.constants.CommonConstants.TAG_KEY; import static org.mockito.Mockito.when; @@ -256,4 +259,47 @@ private TagRouterRule getTagRule() { TagRouterRule tagRouterRule = TagRuleParser.parse(tagRouterRuleConfig); return tagRouterRule; } + + @Test + public void tagMultiLevelTest() { + String tagSelector = "beta|team1|partner1"; + Set address1 = Sets.newHashSet("192.168.5.1:20880"); + Map> tagAddresses = new HashMap<>(); + tagAddresses.put("beta", address1); + Assertions.assertEquals(address1, TagStateRouter.selectAddressByTagLevel(tagAddresses, tagSelector, false)); + + Set address2 = Sets.newHashSet("192.168.5.2:20880"); + tagAddresses.put("beta|team1", address2); + Assertions.assertEquals(address2, TagStateRouter.selectAddressByTagLevel(tagAddresses, tagSelector, false)); + + Set address3 = Sets.newHashSet("192.168.5.3:20880"); + tagAddresses.put("beta|team1|partner1", address3); + Assertions.assertEquals(address3, TagStateRouter.selectAddressByTagLevel(tagAddresses, tagSelector, false)); + + tagSelector = "beta"; + Assertions.assertEquals(address1, TagStateRouter.selectAddressByTagLevel(tagAddresses, tagSelector, false)); + tagSelector = "beta|team1"; + Assertions.assertEquals(address2, TagStateRouter.selectAddressByTagLevel(tagAddresses, tagSelector, false)); + tagSelector = "beta|team1|partner1"; + Assertions.assertEquals(address3, TagStateRouter.selectAddressByTagLevel(tagAddresses, tagSelector, false)); + + tagSelector = "beta2"; + Assertions.assertNull(TagStateRouter.selectAddressByTagLevel(tagAddresses, tagSelector, false)); + tagSelector = "beta|team2"; + Assertions.assertEquals(address1, TagStateRouter.selectAddressByTagLevel(tagAddresses, tagSelector, false)); + tagSelector = "beta|team1|partner2"; + Assertions.assertEquals(address2, TagStateRouter.selectAddressByTagLevel(tagAddresses, tagSelector, false)); + + + } + + @Test + public void tagLevelForceTest() { + Set addresses = Sets.newHashSet("192.168.1.223:20880"); + Map> tagAddresses = new HashMap<>(); + tagAddresses.put("beta", addresses); + Set selectedAddresses = TagStateRouter.selectAddressByTagLevel(tagAddresses, "beta", true); + Assertions.assertEquals(addresses, selectedAddresses); + } + }