blob: 4db393b09ef34267d485f69e39334157b80fdde4 [file] [log] [blame]
/*
* 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.dubbo.rpc.cluster;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
import org.apache.dubbo.common.url.component.ServiceConfigURL;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcInvocation;
import org.apache.dubbo.rpc.cluster.filter.DemoService;
import org.apache.dubbo.rpc.cluster.router.RouterSnapshotSwitcher;
import org.apache.dubbo.rpc.cluster.router.condition.config.AppStateRouter;
import org.apache.dubbo.rpc.cluster.router.condition.config.ListenableStateRouter;
import org.apache.dubbo.rpc.cluster.router.condition.config.ServiceStateRouter;
import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshAppRuleListener;
import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshRuleManager;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import org.apache.dubbo.rpc.model.FrameworkModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.TAG_KEY;
import static org.apache.dubbo.rpc.cluster.router.mesh.route.MeshRuleConstants.MESH_RULE_DATA_ID_SUFFIX;
import static org.mockito.Mockito.when;
public class RouterChainTest {
/**
* verify the router and state router loaded by default
*/
@Test
public void testBuildRouterChain() {
RouterChain<DemoService> routerChain = createRouterChanin();
Assertions.assertEquals(0, routerChain.getRouters().size());
Assertions.assertEquals(5, routerChain.getStateRouters().size());
}
private RouterChain<DemoService> createRouterChanin() {
Map<String, String> parameters = new HashMap<>();
parameters.put(INTERFACE_KEY, DemoService.class.getName());
parameters.put("registry", "zookeeper");
URL url = new ServiceConfigURL("dubbo",
"127.0.0.1",
20881,
DemoService.class.getName(),
parameters);
RouterChain<DemoService> routerChain = RouterChain.buildChain(DemoService.class, url);
return routerChain;
}
@Test
public void testRoute() {
RouterChain<DemoService> routerChain = createRouterChanin();
// mockInvoker will be filtered out by MockInvokersSelector
Invoker<DemoService> mockInvoker = createMockInvoker();
// invoker1 will be filtered out by MeshStateRouter
Map<String, String> map1 = new HashMap<>();
map1.put("env-sign", "yyyyyyy");
Invoker<DemoService> invoker1 = createNormalInvoker(map1);
// invoker2 will be filtered out by TagStateRouter
Map<String, String> map2 = new HashMap<>();
map2.put("env-sign", "xxx");
map2.put("tag1", "hello");
Invoker<DemoService> invoker2 = createNormalInvoker(map2);
// invoker3 will be filtered out by AppStateRouter
Map<String, String> map3 = new HashMap<>();
map3.put("env-sign", "xxx");
map3.put("tag1", "hello");
map3.put(TAG_KEY, "TAG_");
Invoker<DemoService> invoker3 = createNormalInvoker(map3);
// invoker4 will be filtered out by ServiceStateRouter
Map<String, String> map4 = new HashMap<>();
map4.put("env-sign", "xxx");
map4.put("tag1", "hello");
map4.put(TAG_KEY, "TAG_");
map4.put("timeout", "5000");
Invoker<DemoService> invoker4 = createNormalInvoker(map4);
// invoker5 is the only one returned at the end that is not filtered out
Map<String, String> map5 = new HashMap<>();
map5.put("env-sign", "xxx");
map5.put("tag1", "hello");
map5.put(TAG_KEY, "TAG_");
map5.put("timeout", "5000");
map5.put("serialization", "hessian2");
Invoker<DemoService> invoker5 = createNormalInvoker(map5);
BitList<Invoker<DemoService>> invokers = new BitList<>(Arrays.asList(mockInvoker, invoker1, invoker2, invoker3, invoker4, invoker5));
routerChain.setInvokers(invokers);
// mesh rule for MeshStateRouter
MeshRuleManager meshRuleManager = mockInvoker.getUrl().getOrDefaultModuleModel().getBeanFactory().getBean(MeshRuleManager.class);
ConcurrentHashMap<String, MeshAppRuleListener> appRuleListeners = meshRuleManager.getAppRuleListeners();
MeshAppRuleListener meshAppRuleListener = appRuleListeners.get(invoker1.getUrl().getRemoteApplication());
ConfigChangedEvent configChangedEvent = new ConfigChangedEvent("demo-route" + MESH_RULE_DATA_ID_SUFFIX, DynamicConfiguration.DEFAULT_GROUP,
MESH_RULE1 + "---\n" + MESH_RULE2, ConfigChangeType.ADDED);
meshAppRuleListener.process(configChangedEvent);
// condition rule for AppStateRouter&ServiceStateRouter
ListenableStateRouter serviceRouter = routerChain.getStateRouters().stream().filter(s -> s instanceof ServiceStateRouter).map(s -> (ListenableStateRouter) s).findAny().orElse(null);
ConfigChangedEvent serviceConditionEvent = new ConfigChangedEvent(DynamicConfiguration.getRuleKey(mockInvoker.getUrl()) + ".condition-router", DynamicConfiguration.DEFAULT_GROUP,
SERVICE_CONDITION_RULE, ConfigChangeType.ADDED);
serviceRouter.process(serviceConditionEvent);
ListenableStateRouter appRouter = routerChain.getStateRouters().stream().filter(s -> s instanceof AppStateRouter).map(s -> (ListenableStateRouter) s).findAny().orElse(null);
ConfigChangedEvent appConditionEvent = new ConfigChangedEvent("app.condition-router", DynamicConfiguration.DEFAULT_GROUP,
APP_CONDITION_RULE, ConfigChangeType.ADDED);
appRouter.process(appConditionEvent);
// prepare consumerUrl and RpcInvocation
URL consumerUrl = URL.valueOf("consumer://localhost/DemoInterface?remote.application=app1");
RpcInvocation rpcInvocation = new RpcInvocation();
rpcInvocation.setServiceName("DemoService");
rpcInvocation.setObjectAttachment("trafficLabel", "xxx");
rpcInvocation.setObjectAttachment(TAG_KEY, "TAG_");
RpcContext.getServiceContext().setNeedPrintRouterSnapshot(true);
RouterSnapshotSwitcher routerSnapshotSwitcher = FrameworkModel.defaultModel().getBeanFactory().getBean(RouterSnapshotSwitcher.class);
routerSnapshotSwitcher.addEnabledService("org.apache.dubbo.demo.DemoService");
// route
List<Invoker<DemoService>> result = routerChain.route(consumerUrl, invokers, rpcInvocation);
Assertions.assertEquals(result.size(), 1);
Assertions.assertTrue(result.contains(invoker5));
String snapshotLog =
"[ Parent (Input: 6) (Current Node Output: 6) (Chain Node Output: 1) ] Input: localhost:9103,localhost:9103,localhost:9103,localhost:9103,localhost:9103 -> Chain Node Output: localhost:9103...\n" +
" [ MockInvokersSelector (Input: 6) (Current Node Output: 5) (Chain Node Output: 1) Router message: invocation.need.mock not set. Return normal Invokers. ] Current Node Output: localhost:9103,localhost:9103,localhost:9103,localhost:9103,localhost:9103\n" +
" [ StandardMeshRuleRouter (Input: 5) (Current Node Output: 4) (Chain Node Output: 1) Router message: Match App: app Subset: isolation ] Current Node Output: localhost:9103,localhost:9103,localhost:9103,localhost:9103\n" +
" [ TagStateRouter (Input: 4) (Current Node Output: 3) (Chain Node Output: 1) Router message: Disable Tag Router. Reason: tagRouterRule is invalid or disabled ] Current Node Output: localhost:9103,localhost:9103,localhost:9103\n" +
" [ ServiceStateRouter (Input: 3) (Current Node Output: 3) (Chain Node Output: 1) Router message: null ] Current Node Output: localhost:9103,localhost:9103,localhost:9103\n" +
" [ ConditionStateRouter (Input: 3) (Current Node Output: 2) (Chain Node Output: 2) Router message: Match return. ] Current Node Output: localhost:9103,localhost:9103\n" +
" [ AppStateRouter (Input: 2) (Current Node Output: 2) (Chain Node Output: 1) Router message: null ] Current Node Output: localhost:9103,localhost:9103\n" +
" [ ConditionStateRouter (Input: 2) (Current Node Output: 1) (Chain Node Output: 1) Router message: Match return. ] Current Node Output: localhost:9103";
String[] snapshot = routerSnapshotSwitcher.cloneSnapshot();
Assertions.assertTrue(snapshot[0].contains(snapshotLog));
RpcContext.getServiceContext().setNeedPrintRouterSnapshot(false);
result = routerChain.route(consumerUrl, invokers, rpcInvocation);
Assertions.assertEquals(result.size(), 1);
Assertions.assertTrue(result.contains(invoker5));
routerChain.destroy();
Assertions.assertEquals(routerChain.getRouters().size(), 0);
Assertions.assertEquals(routerChain.getStateRouters().size(), 0);
}
private Invoker<DemoService> createMockInvoker() {
URL url = URL.valueOf("mock://localhost:9103/DemoInterface?remote.application=app");
Invoker<DemoService> invoker = Mockito.mock(Invoker.class);
when(invoker.getUrl()).thenReturn(url);
return invoker;
}
private Invoker<DemoService> createNormalInvoker(Map<String, String> parameters) {
URL url = URL.valueOf("dubbo://localhost:9103/DemoInterface?remote.application=app");
if (CollectionUtils.isNotEmptyMap(parameters)) {
url = url.addParameters(parameters);
}
Invoker<DemoService> invoker = Mockito.mock(Invoker.class);
when(invoker.getUrl()).thenReturn(url);
return invoker;
}
private final static String MESH_RULE1 = "apiVersion: service.dubbo.apache.org/v1alpha1\n" +
"kind: DestinationRule\n" +
"metadata: { name: demo-route }\n" +
"spec:\n" +
" host: demo\n" +
" subsets:\n" +
" - labels: { env-sign: xxx, tag1: hello }\n" +
" name: isolation\n" +
" - labels: { env-sign: yyy }\n" +
" name: testing-trunk\n" +
" - labels: { env-sign: zzz }\n" +
" name: testing\n" +
" trafficPolicy:\n" +
" loadBalancer: { simple: ROUND_ROBIN }\n" +
"\n";
private final static String MESH_RULE2 = "apiVersion: service.dubbo.apache.org/v1alpha1\n" +
"kind: VirtualService\n" +
"metadata: { name: demo-route }\n" +
"spec:\n" +
" dubbo:\n" +
" - routedetail:\n" +
" - match:\n" +
" - attachments: \n" +
" dubboContext: {trafficLabel: {regex: xxx}}\n" +
" name: xxx-project\n" +
" route:\n" +
" - destination: {host: demo, subset: isolation}\n" +
" services:\n" +
" - {regex: DemoService}\n" +
" hosts: [demo]\n";
private static final String APP_CONDITION_RULE = "scope: application\n" +
"force: true\n" +
"runtime: false\n" +
"enabled: true\n" +
"priority: 1\n" +
"key: demo-consumer\n" +
"conditions:\n" +
"- => serialization=hessian2";
private static final String SERVICE_CONDITION_RULE = "scope: service\n" +
"force: true\n" +
"runtime: false\n" +
"enabled: true\n" +
"priority: 1\n" +
"key: org.apache.dubbo.demo.DemoService\n" +
"conditions:\n" +
"- => timeout=5000";
}