From 852aa685cc626a4a03c649895ae5ccbfb0008887 Mon Sep 17 00:00:00 2001 From: Susheel Kumar Date: Sat, 28 Jan 2017 08:33:53 -0500 Subject: [PATCH] decouple building url list from CloudSolrClient to separate class for better testability --- .../client/solrj/impl/CloudSolrClient.java | 67 +---- .../client/solrj/impl/SolrUrlListBuilder.java | 147 +++++++++++ .../cloud/rule/ClientSnitchContext.java | 42 ++++ .../solr/common/params/ShardParams.java | 3 + .../solrj/impl/SolrUrlListBuilderTest.java | 228 ++++++++++++++++++ 5 files changed, 428 insertions(+), 59 deletions(-) create mode 100644 solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrUrlListBuilder.java create mode 100644 solr/solrj/src/java/org/apache/solr/common/cloud/rule/ClientSnitchContext.java create mode 100644 solr/solrj/src/test/org/apache/solr/client/solrj/impl/SolrUrlListBuilderTest.java diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java index 3b6948439955..a02c5ab08f33 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java @@ -67,8 +67,10 @@ import org.apache.solr.common.cloud.Replica; import org.apache.solr.common.cloud.Slice; import org.apache.solr.common.cloud.ZkCoreNodeProps; -import org.apache.solr.common.cloud.ZkNodeProps; import org.apache.solr.common.cloud.ZkStateReader; +import org.apache.solr.common.cloud.rule.ClientSnitchContext; +import org.apache.solr.common.cloud.rule.ImplicitSnitch; +import org.apache.solr.common.cloud.rule.SnitchContext; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.ShardParams; import org.apache.solr.common.params.SolrParams; @@ -1271,65 +1273,12 @@ protected NamedList sendRequest(SolrRequest request, String collection) } Set liveNodes = stateProvider.liveNodes(); - List leaderUrlList = null; - List urlList = null; - List replicasList = null; - - // build a map of unique nodes - // TODO: allow filtering by group, role, etc - Map nodes = new HashMap<>(); - List urlList2 = new ArrayList<>(); - for (Slice slice : slices.values()) { - for (ZkNodeProps nodeProps : slice.getReplicasMap().values()) { - ZkCoreNodeProps coreNodeProps = new ZkCoreNodeProps(nodeProps); - String node = coreNodeProps.getNodeName(); - if (!liveNodes.contains(coreNodeProps.getNodeName()) - || Replica.State.getState(coreNodeProps.getState()) != Replica.State.ACTIVE) continue; - if (nodes.put(node, nodeProps) == null) { - if (!sendToLeaders || coreNodeProps.isLeader()) { - String url; - if (reqParams.get(UpdateParams.COLLECTION) == null) { - url = ZkCoreNodeProps.getCoreUrl(nodeProps.getStr(ZkStateReader.BASE_URL_PROP), collection); - } else { - url = coreNodeProps.getCoreUrl(); - } - urlList2.add(url); - } else { - String url; - if (reqParams.get(UpdateParams.COLLECTION) == null) { - url = ZkCoreNodeProps.getCoreUrl(nodeProps.getStr(ZkStateReader.BASE_URL_PROP), collection); - } else { - url = coreNodeProps.getCoreUrl(); - } - replicas.add(url); - } - } - } - } - - if (sendToLeaders) { - leaderUrlList = urlList2; - replicasList = replicas; - } else { - urlList = urlList2; - } - - if (sendToLeaders) { - theUrlList = new ArrayList<>(leaderUrlList.size()); - theUrlList.addAll(leaderUrlList); - } else { - theUrlList = new ArrayList<>(urlList.size()); - theUrlList.addAll(urlList); - } + ImplicitSnitch snitch = new ImplicitSnitch(); + SnitchContext context = new ClientSnitchContext(null, null, new HashMap<>()); + + SolrUrlListBuilder urlSelector = new SolrUrlListBuilder(snitch,context); + theUrlList = urlSelector.buildUrlList(slices, liveNodes, sendToLeaders, reqParams, collection); - Collections.shuffle(theUrlList, rand); - if (sendToLeaders) { - ArrayList theReplicas = new ArrayList<>( - replicasList.size()); - theReplicas.addAll(replicasList); - Collections.shuffle(theReplicas, rand); - theUrlList.addAll(theReplicas); - } if (theUrlList.isEmpty()) { for (String s : collectionNames) { diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrUrlListBuilder.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrUrlListBuilder.java new file mode 100644 index 000000000000..08f249b485b8 --- /dev/null +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrUrlListBuilder.java @@ -0,0 +1,147 @@ +/* + * 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.solr.client.solrj.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.apache.solr.common.cloud.Replica; +import org.apache.solr.common.cloud.Slice; +import org.apache.solr.common.cloud.ZkCoreNodeProps; +import org.apache.solr.common.cloud.ZkNodeProps; +import org.apache.solr.common.cloud.ZkStateReader; +import org.apache.solr.common.cloud.rule.ImplicitSnitch; +import org.apache.solr.common.cloud.rule.SnitchContext; +import org.apache.solr.common.params.ShardParams; +import org.apache.solr.common.params.SolrParams; +import org.apache.solr.common.params.UpdateParams; + +public class SolrUrlListBuilder { + + Random rand = new Random(); + private ImplicitSnitch snitch; + private SnitchContext snitchContext; + + public SolrUrlListBuilder(ImplicitSnitch snitch, SnitchContext snitchContext) + { + this.snitch = snitch; + this.snitchContext = snitchContext; + } + + public List buildUrlList(Map slices, Set liveNodes, boolean sendToLeaders, SolrParams reqParams, String collection ) + { + + List replicas = new ArrayList<>(); + List theUrlList = new ArrayList<>(); + List leaderUrlList = null; + List urlList = null; + List replicasList = null; + + String []routingRules = reqParams.getParams(ShardParams.ROUTING_RULE); + + // build a map of unique nodes + // TODO: allow filtering by group, role, etc + Map nodes = new HashMap<>(); + List urlList2 = new ArrayList<>(); + for (Slice slice : slices.values()) { + for (ZkNodeProps nodeProps : slice.getReplicasMap().values()) { + ZkCoreNodeProps coreNodeProps = new ZkCoreNodeProps(nodeProps); + String node = coreNodeProps.getNodeName(); + if((routingRules!=null && routingRules.length!=0) && !nodeMatchRoutingRule(node,routingRules,snitch,snitchContext)) + continue; + if (!liveNodes.contains(coreNodeProps.getNodeName()) + || Replica.State.getState(coreNodeProps.getState()) != Replica.State.ACTIVE) continue; + if (nodes.put(node, nodeProps) == null) { + if (!sendToLeaders || coreNodeProps.isLeader()) { + String url; + if (reqParams.get(UpdateParams.COLLECTION) == null) { + url = ZkCoreNodeProps.getCoreUrl(nodeProps.getStr(ZkStateReader.BASE_URL_PROP), collection); + } else { + url = coreNodeProps.getCoreUrl(); + } + urlList2.add(url); + } else { + String url; + if (reqParams.get(UpdateParams.COLLECTION) == null) { + url = ZkCoreNodeProps.getCoreUrl(nodeProps.getStr(ZkStateReader.BASE_URL_PROP), collection); + } else { + url = coreNodeProps.getCoreUrl(); + } + replicas.add(url); + } + } + } + } + + if (sendToLeaders) { + leaderUrlList = urlList2; + replicasList = replicas; + } else { + urlList = urlList2; + } + + if (sendToLeaders) { + theUrlList = new ArrayList<>(leaderUrlList.size()); + theUrlList.addAll(leaderUrlList); + } else { + theUrlList = new ArrayList<>(urlList.size()); + theUrlList.addAll(urlList); + } + + Collections.shuffle(theUrlList, rand); + if (sendToLeaders) { + ArrayList theReplicas = new ArrayList<>( + replicasList.size()); + theReplicas.addAll(replicasList); + Collections.shuffle(theReplicas, rand); + theUrlList.addAll(theReplicas); + } + + return theUrlList; + } + + private boolean nodeMatchRoutingRule(String node, String[] routingRules, ImplicitSnitch snitch, + SnitchContext context) { + HashMap routingRulesMap = StringArrayToHashMap(routingRules); + + //get tags associated with this node + snitch.getTags(node, routingRulesMap.keySet(), context); + Map tags = context.getTags(); + + for(String tag : tags.keySet()) + { + String ip = routingRulesMap.get(tag); + if(!ip.equals(tags.get(tag))) return false; + } + return true; + } + + private HashMap StringArrayToHashMap(String[] routingRules) { + HashMap routingRulesMap = new HashMap<>(); + for(String routingRule : routingRules) + { + String []rule = routingRule.split(":"); + routingRulesMap.put(rule[0], rule[1]); + } + return routingRulesMap; + } +} diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/rule/ClientSnitchContext.java b/solr/solrj/src/java/org/apache/solr/common/cloud/rule/ClientSnitchContext.java new file mode 100644 index 000000000000..d0cba147f07f --- /dev/null +++ b/solr/solrj/src/java/org/apache/solr/common/cloud/rule/ClientSnitchContext.java @@ -0,0 +1,42 @@ +/* + * 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.solr.common.cloud.rule; + +import java.util.Map; + +import org.apache.solr.common.params.ModifiableSolrParams; + +public class ClientSnitchContext extends SnitchContext { + + public ClientSnitchContext(SnitchInfo perSnitch, String node, Map session) { + super(perSnitch, node, session); + } + + @Override + public Map getZkJson(String path) { + return null; + } + + @Override + public void invokeRemote(String node, ModifiableSolrParams params, String klas, RemoteCallback callback) {} + + + +} + + + diff --git a/solr/solrj/src/java/org/apache/solr/common/params/ShardParams.java b/solr/solrj/src/java/org/apache/solr/common/params/ShardParams.java index 26865737a92e..9face03f0234 100644 --- a/solr/solrj/src/java/org/apache/solr/common/params/ShardParams.java +++ b/solr/solrj/src/java/org/apache/solr/common/params/ShardParams.java @@ -56,4 +56,7 @@ public interface ShardParams { /** Force a single-pass distributed query? (true/false) */ public static final String DISTRIB_SINGLE_PASS = "distrib.singlePass"; + + /** Routing Rules */ + public static final String ROUTING_RULE = "routingRule"; } diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/SolrUrlListBuilderTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/SolrUrlListBuilderTest.java new file mode 100644 index 000000000000..6e4f873d896f --- /dev/null +++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/SolrUrlListBuilderTest.java @@ -0,0 +1,228 @@ +/* + * 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.solr.client.solrj.impl; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.common.cloud.Replica; +import org.apache.solr.common.cloud.Slice; +import org.apache.solr.common.cloud.rule.ClientSnitchContext; +import org.apache.solr.common.cloud.rule.ImplicitSnitch; +import org.apache.solr.common.cloud.rule.SnitchContext; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import static org.mockito.Mockito.when; +import static org.junit.Assert.assertThat; +import static org.hamcrest.core.Is.is; + +public class SolrUrlListBuilderTest { + + private ImplicitSnitch snitch; + private SnitchContext context; + + private static final String IP_1 = "ip_1"; + private static final String IP_2 = "ip_2"; + private static final String IP_3 = "ip_3"; + private static final String IP_4 = "ip_4"; + + + @Before + public void beforeSolrUrlListBuilderTest() { + snitch = new ImplicitSnitch(); + context = new ClientSnitchContext(null, null, new HashMap<>()); + } + + + @Test + public void testBuildUrlList() + { + + SolrUrlListBuilder urlSelector = new SolrUrlListBuilder(snitch,context); + + Map slices = new HashMap<>(); + Map replicas = new HashMap<>(); + + Map propMap = new HashMap<>(); + propMap.put("core", "COL_shard1_replica1"); + propMap.put("base_url", "http://server1:8983/solr"); + propMap.put("node_name", "server1:8983_solr"); + propMap.put("state", "active"); + Replica replica = new Replica("core_node1", propMap); + replicas.put("core_node1", replica); + + propMap = new HashMap<>(); + propMap.put("core", "COL_shard1_replica2"); + propMap.put("base_url", "http://server2:8983/solr"); + propMap.put("node_name", "server2:8983_solr"); + propMap.put("state", "active"); + propMap.put("leader", "true"); + replica = new Replica("core_node2", propMap); + replicas.put("core_node2", replica); + + Slice slice= new Slice("shard1", replicas, null); + + slices.put("COL_shard1", slice); + Set liveNodes = new HashSet<>(); + liveNodes.add("server1:8983_solr"); + liveNodes.add("server2:8983_solr"); + + boolean sendToLeaders = false; + String collection = "COL"; + SolrQuery query = new SolrQuery("*:*"); + + List urlList = urlSelector.buildUrlList(slices, liveNodes, sendToLeaders, query, collection); + Assert.assertEquals(urlList.size(), 2); + + //Add one more slice + + propMap = new HashMap<>(); + propMap.put("core", "COL_shard2_replica1"); + propMap.put("base_url", "http://server3:8983/solr"); + propMap.put("node_name", "server3:8983_solr"); + propMap.put("state", "active"); + replica = new Replica("core_node3", propMap); + replicas.put("core_node3", replica); + + propMap = new HashMap<>(); + propMap.put("core", "COL_shard2_replica2"); + propMap.put("base_url", "http://server4:8983/solr"); + propMap.put("node_name", "server4:8983_solr"); + propMap.put("state", "active"); + propMap.put("leader", "true"); + replica = new Replica("core_node4", propMap); + replicas.put("core_node4", replica); + + slice= new Slice("shard2", replicas, null); + slices.put("COL_shard2", slice); + // liveNodes = new HashSet<>(); + liveNodes.add("server3:8983_solr"); + liveNodes.add("server4:8983_solr"); + + urlList = urlSelector.buildUrlList(slices, liveNodes, sendToLeaders, query, collection); + + assertThat(urlList.size(), is(4)); + + } + + @Test + public void testBuildUrlListForRoutingRules() + { + ImplicitSnitch mockedSnitch = Mockito.spy(snitch); + + when(mockedSnitch.getHostIp("serv01.dc01.ny.us.apache.org")).thenReturn("10.1.12.101"); + when(mockedSnitch.getHostIp("serv01.dc02.ny.us.apache.org")).thenReturn("10.2.12.101"); + when(mockedSnitch.getHostIp("serv02.dc01.ny.us.apache.org")).thenReturn("10.1.12.102"); + when(mockedSnitch.getHostIp("serv02.dc02.ny.us.apache.org")).thenReturn("10.2.12.102"); + + + SolrUrlListBuilder urlSelector = new SolrUrlListBuilder(mockedSnitch,context); + + Map slices = new HashMap<>(); + Map replicas = new HashMap<>(); + Map propMap = new HashMap<>(); + + //replica1 + propMap.put("core", "COL_shard1_replica1"); + propMap.put("base_url", "http://serv01.dc01.ny.us.apache.org:8983/solr"); + propMap.put("node_name", "serv01.dc01.ny.us.apache.org:8983_solr"); + propMap.put("state", "active"); + Replica replica = new Replica("core_node1", propMap); + replicas.put("core_node1", replica); + + //replica2 + propMap = new HashMap<>(); + propMap.put("core", "COL_shard1_replica2"); + propMap.put("base_url", "http://serv01.dc02.ny.us.apache.org:8983/solr"); + propMap.put("node_name", "serv01.dc02.ny.us.apache.org:8983_solr"); + propMap.put("state", "active"); + propMap.put("leader", "true"); + replica = new Replica("core_node2", propMap); + replicas.put("core_node2", replica); + + //slice1 + Slice slice= new Slice("shard1", replicas, null); + slices.put("COL_shard1", slice); + + + Set liveNodes = new HashSet<>(); + + //livenodes + liveNodes.add("serv01.dc01.ny.us.apache.org:8983_solr"); + liveNodes.add("serv01.dc02.ny.us.apache.org:8983_solr"); + + boolean sendToLeaders = false; + String collection = "COL"; + SolrQuery query = new SolrQuery("*:*"); + + //routing rules to query with ip 10.1.12.* selecting + // only shard1 replica1 + query.add("routingRule","ip_4:10"); + query.add("routingRule","ip_3:1"); + query.add("routingRule","ip_2:12"); + + //buildURL List + List urlList = urlSelector.buildUrlList(slices, liveNodes, sendToLeaders, query, collection); + assertThat(urlList.size(), is(1)); + + //replica3 + propMap = new HashMap<>(); + propMap.put("core", "COL_shard2_replica1"); + propMap.put("base_url", "http://serv02.dc01.ny.us.apache.org:8983/solr"); + propMap.put("node_name", "serv02.dc01.ny.us.apache.org:8983_solr"); + propMap.put("state", "active"); + replica = new Replica("core_node3", propMap); + replicas.put("core_node3", replica); + + //replica4 + propMap = new HashMap<>(); + propMap.put("core", "COL_shard2_replica2"); + propMap.put("base_url", "http://serv02.dc02.ny.us.apache.org:8983/solr"); + propMap.put("node_name", "serv02.dc02.ny.us.apache.org:8983_solr"); + propMap.put("state", "active"); + propMap.put("leader", "true"); + replica = new Replica("core_node4", propMap); + replicas.put("core_node4", replica); + + slice= new Slice("shard2", replicas, null); + slices.put("COL_shard2", slice); + + //livenodes + liveNodes.add("serv02.dc01.ny.us.apache.org:8983_solr"); + liveNodes.add("serv02.dc02.ny.us.apache.org:8983_solr"); + + query = new SolrQuery("*:*"); + + //routing rules to query with ip 10.1.12.* selecting + // shards with dc01 + query.add("routingRule","ip_4:10"); + query.add("routingRule","ip_3:1"); + query.add("routingRule","ip_2:12"); + + //buildURL List + urlList = urlSelector.buildUrlList(slices, liveNodes, sendToLeaders, query, collection); + assertThat(urlList.size(), is(2)); + + } + +}