From a5b222d011a34405f42bf96158e00c10c7502b67 Mon Sep 17 00:00:00 2001 From: Cheng YouLing <97039406+chengyouling@users.noreply.github.com> Date: Thu, 21 Sep 2023 20:26:56 +0800 Subject: [PATCH] [SCB-2811] support global route rule for gray (#3948) (#3949) --- .../router/cache/RouterRuleCache.java | 16 +- .../RouterDistributorGlobalConfigTest.java | 207 ++++++++++++++++++ 2 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 governance/src/test/java/org/apache/servicecomb/router/RouterDistributorGlobalConfigTest.java diff --git a/governance/src/main/java/org/apache/servicecomb/router/cache/RouterRuleCache.java b/governance/src/main/java/org/apache/servicecomb/router/cache/RouterRuleCache.java index 8ff480cead..80da3326a7 100644 --- a/governance/src/main/java/org/apache/servicecomb/router/cache/RouterRuleCache.java +++ b/governance/src/main/java/org/apache/servicecomb/router/cache/RouterRuleCache.java @@ -49,6 +49,8 @@ public class RouterRuleCache { private static final String ROUTE_RULE = "servicecomb.routeRule.%s"; + public static final String GLOBAL_ROUTE_RULE_KEY = "servicecomb.globalRouteRule"; + private final Environment environment; private final ConcurrentHashMap serviceInfoCacheMap = new ConcurrentHashMap<>(); @@ -78,7 +80,7 @@ public boolean doInit(String targetServiceName) { if (serviceInfoCacheMap.containsKey(targetServiceName)) { return true; } - return addAllRule(targetServiceName, environment.getProperty(String.format(ROUTE_RULE, targetServiceName), "")); + return addAllRule(targetServiceName); } } return true; @@ -90,10 +92,17 @@ public void onConfigurationChangedEvent(GovernanceConfigurationChangedEvent even if (key.startsWith(ROUTE_RULE_PREFIX)) { serviceInfoCacheMap.remove(key.substring(ROUTE_RULE_PREFIX.length())); } + if (key.equals(GLOBAL_ROUTE_RULE_KEY)) { + serviceInfoCacheMap.clear(); + } } } - private boolean addAllRule(String targetServiceName, String ruleStr) { + private boolean addAllRule(String targetServiceName) { + String ruleStr = environment.getProperty(String.format(ROUTE_RULE, targetServiceName), ""); + if (StringUtils.isEmpty(ruleStr)) { + ruleStr = environment.getProperty(GLOBAL_ROUTE_RULE_KEY, ""); + } if (StringUtils.isEmpty(ruleStr)) { return false; } @@ -121,7 +130,8 @@ private boolean addAllRule(String targetServiceName, String ruleStr) { * if a server don't have rule , avoid registered too many callback , it may cause memory leak */ private boolean isServerContainRule(String targetServiceName) { - return !StringUtils.isEmpty(environment.getProperty(String.format(ROUTE_RULE, targetServiceName), "")); + return !StringUtils.isEmpty(environment.getProperty(String.format(ROUTE_RULE, targetServiceName), "")) || + !StringUtils.isEmpty(environment.getProperty(GLOBAL_ROUTE_RULE_KEY, "")); } public ConcurrentHashMap getServiceInfoCacheMap() { diff --git a/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorGlobalConfigTest.java b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorGlobalConfigTest.java new file mode 100644 index 0000000000..587e7fc7d5 --- /dev/null +++ b/governance/src/test/java/org/apache/servicecomb/router/RouterDistributorGlobalConfigTest.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.servicecomb.router; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.servicecomb.governance.event.GovernanceConfigurationChangedEvent; +import org.apache.servicecomb.governance.event.GovernanceEventManager; +import org.apache.servicecomb.router.cache.RouterRuleCache; +import org.apache.servicecomb.router.distribute.RouterDistributor; +import org.junit.Before; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.Environment; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@ContextConfiguration(locations = "classpath:META-INF/spring/*.xml", initializers = ConfigDataApplicationContextInitializer.class) +public class RouterDistributorGlobalConfigTest { + private static final String TARGET_SERVICE_NAME = "test_server"; + + public static final String CONFIG_KEY = RouterRuleCache.ROUTE_RULE_PREFIX + TARGET_SERVICE_NAME; + + private static final String RULE_STRING = "" + + " - precedence: 1\n" + + " match:\n" + + " headers: #header匹配\n" + + " appId:\n" + + " regex: 01\n" + + " caseInsensitive: false # 是否区分大小写,默认为false,区分大小写\n" + + " userId:\n" + + " exact: 02\n" + + " route:\n" + + " - weight: 100\n" + + " tags:\n" + + " version: 2.0\n" + + " - precedence: 2\n" + + " match:\n" + + " headers: #header匹配\n" + + " appId:\n" + + " regex: 01\n" + + " caseInsensitive: false # 是否区分大小写,默认为false,区分大小写\n" + + " userId:\n" + + " exact: 03\n" + + " route:\n" + + " - weight: 100\n" + + " tags:\n" + + " version: 1.0\n"; + + private Environment environment; + + private RouterFilter routerFilter; + + private RouterDistributor testDistributor; + + @Autowired + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + @Autowired + public void setRouterFilter(RouterFilter routerFilter) { + this.routerFilter = routerFilter; + } + + @Autowired + public void setTestDistributor(RouterDistributor testDistributor) { + this.testDistributor = testDistributor; + } + + public RouterDistributorGlobalConfigTest() { + } + + private final Map dynamicValues = new HashMap<>(); + + @Before + public void setUp() { + ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) environment; + + if (configurableEnvironment.getPropertySources().contains("testDynamicChange")) { + configurableEnvironment.getPropertySources().remove("testDynamicChange"); + } + + configurableEnvironment.getPropertySources() + .addFirst(new EnumerablePropertySource>("testDynamicChange", dynamicValues) { + @Override + public Object getProperty(String s) { + return this.getSource().get(s); + } + + @Override + public String[] getPropertyNames() { + return this.getSource().keySet().toArray(new String[0]); + } + }); + + dynamicValues.put(RouterRuleCache.GLOBAL_ROUTE_RULE_KEY, RULE_STRING); + postConfigurationChangedEvent(RouterRuleCache.GLOBAL_ROUTE_RULE_KEY); + } + + @Test + public void testUseGlobalRouteRule() { + Map headers = new HashMap<>(); + headers.put("userId", "03"); + headers.put("appId", "01"); + + List serverList = new ArrayList<>(); + ServiceIns ins1 = new ServiceIns("01", TARGET_SERVICE_NAME); + ins1.setVersion("1.0"); + ServiceIns ins2 = new ServiceIns("02", TARGET_SERVICE_NAME); + ins2.addTags("app", "a"); + ins2.setVersion("2.0"); + serverList.add(ins1); + serverList.add(ins2); + + List resultServerList = mainFilter(serverList, headers); + Assertions.assertEquals(1, resultServerList.size()); + Assertions.assertEquals("01", resultServerList.get(0).getId()); + } + + @Test + public void testUseProviderRouteRule() { + String rule = "" + + " - precedence: 1\n" + + " match:\n" + + " headers: #header匹配\n" + + " appId:\n" + + " regex: 01\n" + + " caseInsensitive: false # 是否区分大小写,默认为false,区分大小写\n" + + " userId:\n" + + " exact: 03\n" + + " route:\n" + + " - weight: 100\n" + + " tags:\n" + + " version: 2.0\n" + + " - precedence: 2\n" + + " match:\n" + + " headers: #header匹配\n" + + " appId:\n" + + " regex: 01\n" + + " caseInsensitive: false # 是否区分大小写,默认为false,区分大小写\n" + + " userId:\n" + + " exact: 02\n" + + " route:\n" + + " - weight: 100\n" + + " tags:\n" + + " version: 1.0\n"; + dynamicValues.put(CONFIG_KEY, rule); + postConfigurationChangedEvent(CONFIG_KEY); + + Map headers = new HashMap<>(); + headers.put("userId", "03"); + headers.put("appId", "01"); + + List serverList = new ArrayList<>(); + ServiceIns ins1 = new ServiceIns("01", TARGET_SERVICE_NAME); + ins1.setVersion("1.0"); + ServiceIns ins2 = new ServiceIns("02", TARGET_SERVICE_NAME); + ins2.addTags("app", "a"); + ins2.setVersion("2.0"); + serverList.add(ins1); + serverList.add(ins2); + + List resultServerList = mainFilter(serverList, headers); + Assertions.assertEquals(1, resultServerList.size()); + Assertions.assertEquals("02", resultServerList.get(0).getId()); + } + + private List mainFilter(List serverList, Map headers) { + return routerFilter + .getFilteredListOfServers(serverList, TARGET_SERVICE_NAME, headers, + testDistributor); + } + + private void postConfigurationChangedEvent(String changKey) { + Set changedKeys = new HashSet<>(); + changedKeys.add(changKey); + GovernanceConfigurationChangedEvent newEvent = new GovernanceConfigurationChangedEvent(changedKeys); + GovernanceEventManager.post(newEvent); + } +}