mirror of https://github.com/apache/lucene.git
SOLR-14930: Deprecate rulebased replica placement strategy (remove in 9.0) (#1980)
This commit is contained in:
parent
2a3da99e2d
commit
321b4fa0de
|
@ -130,6 +130,8 @@ Other Changes
|
||||||
|
|
||||||
* SOLR-10370: ReplicationHandler should fetch index at fixed delay instead of fixed rate (Cao Manh Dat)
|
* SOLR-10370: ReplicationHandler should fetch index at fixed delay instead of fixed rate (Cao Manh Dat)
|
||||||
|
|
||||||
|
* SOLR-14930: Removed rule based replica placement (noble)
|
||||||
|
|
||||||
Bug Fixes
|
Bug Fixes
|
||||||
---------------------
|
---------------------
|
||||||
* SOLR-14546: Fix for a relatively hard to hit issue in OverseerTaskProcessor that could lead to out of order execution
|
* SOLR-14546: Fix for a relatively hard to hit issue in OverseerTaskProcessor that could lead to out of order execution
|
||||||
|
|
|
@ -456,7 +456,6 @@ public class Overseer implements SolrCloseable {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MODIFYCOLLECTION:
|
case MODIFYCOLLECTION:
|
||||||
CollectionsHandler.verifyRuleParams(zkController.getCoreContainer() ,message.getProperties());
|
|
||||||
return Collections.singletonList(new CollectionMutator(getSolrCloudManager()).modifyCollection(clusterState,message));
|
return Collections.singletonList(new CollectionMutator(getSolrCloudManager()).modifyCollection(clusterState,message));
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("unknown operation:" + operation
|
throw new RuntimeException("unknown operation:" + operation
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package org.apache.solr.cloud.api.collections;
|
package org.apache.solr.cloud.api.collections;
|
||||||
|
|
||||||
import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler.CREATE_NODE_SET;
|
import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler.CREATE_NODE_SET;
|
||||||
import static org.apache.solr.common.cloud.DocCollection.SNITCH;
|
|
||||||
import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP;
|
import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -27,7 +26,6 @@ import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -42,8 +40,6 @@ import org.apache.solr.client.solrj.cloud.SolrCloudManager;
|
||||||
import org.apache.solr.client.solrj.cloud.AlreadyExistsException;
|
import org.apache.solr.client.solrj.cloud.AlreadyExistsException;
|
||||||
import org.apache.solr.client.solrj.cloud.BadVersionException;
|
import org.apache.solr.client.solrj.cloud.BadVersionException;
|
||||||
import org.apache.solr.client.solrj.cloud.VersionedData;
|
import org.apache.solr.client.solrj.cloud.VersionedData;
|
||||||
import org.apache.solr.cloud.rule.ReplicaAssigner;
|
|
||||||
import org.apache.solr.cloud.rule.Rule;
|
|
||||||
import org.apache.solr.cluster.placement.PlacementPlugin;
|
import org.apache.solr.cluster.placement.PlacementPlugin;
|
||||||
import org.apache.solr.cluster.placement.impl.PlacementPluginAssignStrategy;
|
import org.apache.solr.cluster.placement.impl.PlacementPluginAssignStrategy;
|
||||||
import org.apache.solr.cluster.placement.impl.PlacementPluginConfigImpl;
|
import org.apache.solr.cluster.placement.impl.PlacementPluginConfigImpl;
|
||||||
|
@ -493,58 +489,6 @@ public class Assign {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RulesBasedAssignStrategy implements AssignStrategy {
|
|
||||||
public List<Rule> rules;
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
public List snitches;
|
|
||||||
public ClusterState clusterState;
|
|
||||||
|
|
||||||
public RulesBasedAssignStrategy(List<Rule> rules, @SuppressWarnings({"rawtypes"})List snitches, ClusterState clusterState) {
|
|
||||||
this.rules = rules;
|
|
||||||
this.snitches = snitches;
|
|
||||||
this.clusterState = clusterState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ReplicaPosition> assign(SolrCloudManager solrCloudManager, AssignRequest assignRequest) throws Assign.AssignmentException, IOException, InterruptedException {
|
|
||||||
if (assignRequest.numTlogReplicas + assignRequest.numPullReplicas != 0) {
|
|
||||||
throw new Assign.AssignmentException(
|
|
||||||
Replica.Type.TLOG + " or " + Replica.Type.PULL + " replica types not supported with placement rules or cluster policies");
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Integer> shardVsReplicaCount = new HashMap<>();
|
|
||||||
for (String shard : assignRequest.shardNames) shardVsReplicaCount.put(shard, assignRequest.numNrtReplicas);
|
|
||||||
|
|
||||||
Map<String, Map<String, Integer>> shardVsNodes = new LinkedHashMap<>();
|
|
||||||
DocCollection docCollection = solrCloudManager.getClusterStateProvider().getClusterState().getCollectionOrNull(assignRequest.collectionName);
|
|
||||||
if (docCollection != null) {
|
|
||||||
for (Slice slice : docCollection.getSlices()) {
|
|
||||||
LinkedHashMap<String, Integer> n = new LinkedHashMap<>();
|
|
||||||
shardVsNodes.put(slice.getName(), n);
|
|
||||||
for (Replica replica : slice.getReplicas()) {
|
|
||||||
Integer count = n.get(replica.getNodeName());
|
|
||||||
if (count == null) count = 0;
|
|
||||||
n.put(replica.getNodeName(), ++count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> nodesList = assignRequest.nodes == null ? new ArrayList<>(clusterState.getLiveNodes()) : assignRequest.nodes;
|
|
||||||
|
|
||||||
ReplicaAssigner replicaAssigner = new ReplicaAssigner(rules,
|
|
||||||
shardVsReplicaCount,
|
|
||||||
snitches,
|
|
||||||
shardVsNodes,
|
|
||||||
nodesList,
|
|
||||||
solrCloudManager, clusterState);
|
|
||||||
|
|
||||||
Map<ReplicaPosition, String> nodeMappings = replicaAssigner.getNodeMappings();
|
|
||||||
return nodeMappings.entrySet().stream()
|
|
||||||
.map(e -> new ReplicaPosition(e.getKey().shard, e.getKey().index, e.getKey().type, e.getValue()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the appropriate instance of {@link AssignStrategy} based on how the cluster and/or individual collections are
|
* Creates the appropriate instance of {@link AssignStrategy} based on how the cluster and/or individual collections are
|
||||||
* configured.
|
* configured.
|
||||||
|
@ -553,22 +497,10 @@ public class Assign {
|
||||||
PlacementPlugin plugin = PlacementPluginConfigImpl.getPlacementPlugin(solrCloudManager);
|
PlacementPlugin plugin = PlacementPluginConfigImpl.getPlacementPlugin(solrCloudManager);
|
||||||
|
|
||||||
if (plugin != null) {
|
if (plugin != null) {
|
||||||
// If a cluster wide placement plugin is configured (and that's the only way to define a placement plugin), it overrides
|
// If a cluster wide placement plugin is configured (and that's the only way to define a placement plugin)
|
||||||
// per collection configuration (i.e. rules are ignored)
|
|
||||||
return new PlacementPluginAssignStrategy(collection, plugin);
|
return new PlacementPluginAssignStrategy(collection, plugin);
|
||||||
} else {
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
List<Map> ruleMaps = (List<Map>) collection.get(DocCollection.RULE);
|
|
||||||
|
|
||||||
if (ruleMaps != null && !ruleMaps.isEmpty()) {
|
|
||||||
List<Rule> rules = new ArrayList<>();
|
|
||||||
for (Object map : ruleMaps) rules.add(new Rule((Map) map));
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
List snitches = (List) collection.get(SNITCH);
|
|
||||||
return new RulesBasedAssignStrategy(rules, snitches, clusterState);
|
|
||||||
} else {
|
} else {
|
||||||
return new LegacyAssignStrategy();
|
return new LegacyAssignStrategy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,6 @@ import org.apache.zookeeper.KeeperException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import static org.apache.solr.common.cloud.DocCollection.SNITCH;
|
|
||||||
import static org.apache.solr.common.cloud.ZkStateReader.BASE_URL_PROP;
|
import static org.apache.solr.common.cloud.ZkStateReader.BASE_URL_PROP;
|
||||||
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
|
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
|
||||||
import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP;
|
import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP;
|
||||||
|
@ -145,8 +144,6 @@ public class OverseerCollectionMessageHandler implements OverseerMessageHandler,
|
||||||
ZkStateReader.NRT_REPLICAS, "1",
|
ZkStateReader.NRT_REPLICAS, "1",
|
||||||
ZkStateReader.TLOG_REPLICAS, "0",
|
ZkStateReader.TLOG_REPLICAS, "0",
|
||||||
ZkStateReader.PULL_REPLICAS, "0",
|
ZkStateReader.PULL_REPLICAS, "0",
|
||||||
DocCollection.RULE, null,
|
|
||||||
SNITCH, null,
|
|
||||||
WITH_COLLECTION, null,
|
WITH_COLLECTION, null,
|
||||||
COLOCATED_WITH, null));
|
COLOCATED_WITH, null));
|
||||||
|
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.cloud.rule;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.solr.core.CoreContainer;
|
|
||||||
import org.apache.solr.handler.admin.CoreAdminHandler;
|
|
||||||
import org.apache.solr.request.SolrQueryRequest;
|
|
||||||
|
|
||||||
import static org.apache.solr.common.cloud.rule.ImplicitSnitch.CORES;
|
|
||||||
import static org.apache.solr.common.cloud.rule.ImplicitSnitch.DISK;
|
|
||||||
import static org.apache.solr.common.cloud.rule.ImplicitSnitch.SYSPROP;
|
|
||||||
|
|
||||||
//this is the server-side component which provides the tag values
|
|
||||||
public class ImplicitSnitch implements CoreAdminHandler.Invocable {
|
|
||||||
|
|
||||||
static long getUsableSpaceInGB(Path path) throws IOException {
|
|
||||||
long space = Files.getFileStore(path).getUsableSpace();
|
|
||||||
long spaceInGB = space / 1024 / 1024 / 1024;
|
|
||||||
return spaceInGB;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> invoke(SolrQueryRequest req) {
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
CoreContainer cc = (CoreContainer) req.getContext().get(CoreContainer.class.getName());
|
|
||||||
if (req.getParams().getInt(CORES, -1) == 1) {
|
|
||||||
result.put(CORES, cc.getLoadedCoreNames().size());
|
|
||||||
}
|
|
||||||
if (req.getParams().getInt(DISK, -1) == 1) {
|
|
||||||
try {
|
|
||||||
final long spaceInGB = getUsableSpaceInGB(cc.getCoreRootDirectory());
|
|
||||||
result.put(DISK, spaceInGB);
|
|
||||||
} catch (IOException e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String[] sysProps = req.getParams().getParams(SYSPROP);
|
|
||||||
if (sysProps != null && sysProps.length > 0) {
|
|
||||||
for (String prop : sysProps) result.put(SYSPROP + prop, System.getProperty(prop));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,452 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.cloud.rule;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.BitSet;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
|
|
||||||
import org.apache.solr.common.SolrException;
|
|
||||||
import org.apache.solr.common.cloud.ClusterState;
|
|
||||||
import org.apache.solr.common.cloud.DocCollection;
|
|
||||||
import org.apache.solr.common.cloud.Replica;
|
|
||||||
import org.apache.solr.common.cloud.ReplicaPosition;
|
|
||||||
import org.apache.solr.common.cloud.Slice;
|
|
||||||
import org.apache.solr.common.cloud.rule.ImplicitSnitch;
|
|
||||||
import org.apache.solr.common.cloud.rule.Snitch;
|
|
||||||
import org.apache.solr.common.cloud.rule.SnitchContext;
|
|
||||||
import org.apache.solr.common.util.Utils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.MatchStatus.NODE_CAN_BE_ASSIGNED;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.MatchStatus.NOT_APPLICABLE;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.Phase.ASSIGN;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.Phase.FUZZY_ASSIGN;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.Phase.FUZZY_VERIFY;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.Phase.VERIFY;
|
|
||||||
import static org.apache.solr.common.util.Utils.getDeepCopy;
|
|
||||||
|
|
||||||
public class ReplicaAssigner {
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
|
||||||
List<Rule> rules;
|
|
||||||
Map<String, Integer> shardVsReplicaCount;
|
|
||||||
Map<String, Map<String, Object>> nodeVsTags;
|
|
||||||
Map<String, HashMap<String, Integer>> shardVsNodes;
|
|
||||||
List<String> participatingLiveNodes;
|
|
||||||
Set<String> tagNames = new HashSet<>();
|
|
||||||
private Map<String, AtomicInteger> nodeVsCores = new HashMap<>();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param shardVsReplicaCount shard names vs no:of replicas required for each of those shards
|
|
||||||
* @param snitches snitches details
|
|
||||||
* @param shardVsNodes The current state of the system. can be an empty map if no nodes
|
|
||||||
* are created in this collection till now
|
|
||||||
*/
|
|
||||||
@SuppressWarnings({"unchecked"})
|
|
||||||
public ReplicaAssigner(List<Rule> rules,
|
|
||||||
Map<String, Integer> shardVsReplicaCount,
|
|
||||||
@SuppressWarnings({"rawtypes"})List snitches,
|
|
||||||
Map<String, Map<String, Integer>> shardVsNodes,
|
|
||||||
List<String> participatingLiveNodes,
|
|
||||||
SolrCloudManager cloudManager, ClusterState clusterState) {
|
|
||||||
this.rules = rules;
|
|
||||||
for (Rule rule : rules) tagNames.add(rule.tag.name);
|
|
||||||
this.shardVsReplicaCount = shardVsReplicaCount;
|
|
||||||
this.participatingLiveNodes = new ArrayList<>(participatingLiveNodes);
|
|
||||||
this.nodeVsTags = getTagsForNodes(cloudManager, snitches);
|
|
||||||
this.shardVsNodes = getDeepCopy(shardVsNodes, 2);
|
|
||||||
|
|
||||||
if (clusterState != null) {
|
|
||||||
Map<String, DocCollection> collections = clusterState.getCollectionsMap();
|
|
||||||
for (Map.Entry<String, DocCollection> entry : collections.entrySet()) {
|
|
||||||
DocCollection coll = entry.getValue();
|
|
||||||
for (Slice slice : coll.getSlices()) {
|
|
||||||
for (Replica replica : slice.getReplicas()) {
|
|
||||||
AtomicInteger count = nodeVsCores.get(replica.getNodeName());
|
|
||||||
if (count == null) nodeVsCores.put(replica.getNodeName(), count = new AtomicInteger());
|
|
||||||
count.incrementAndGet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Map<String, Object>> getNodeVsTags() {
|
|
||||||
return nodeVsTags;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For each shard return a new set of nodes where the replicas need to be created satisfying
|
|
||||||
* the specified rule
|
|
||||||
*/
|
|
||||||
public Map<ReplicaPosition, String> getNodeMappings() {
|
|
||||||
Map<ReplicaPosition, String> result = getNodeMappings0();
|
|
||||||
if (result == null) {
|
|
||||||
String msg = "Could not identify nodes matching the rules " + rules;
|
|
||||||
if (!failedNodes.isEmpty()) {
|
|
||||||
Map<String, String> failedNodes = new HashMap<>();
|
|
||||||
for (Map.Entry<String, SnitchContext> e : this.failedNodes.entrySet()) {
|
|
||||||
failedNodes.put(e.getKey(), e.getValue().getErrMsg());
|
|
||||||
}
|
|
||||||
msg += " Some nodes where excluded from assigning replicas because tags could not be obtained from them " + failedNodes;
|
|
||||||
}
|
|
||||||
msg += "\n tag values" + Utils.toJSONString(getNodeVsTags());
|
|
||||||
if (!shardVsNodes.isEmpty()) {
|
|
||||||
msg += "\nInitial state for the coll : " + Utils.toJSONString(shardVsNodes);
|
|
||||||
}
|
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, msg);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<ReplicaPosition, String> getNodeMappings0() {
|
|
||||||
List<String> shardNames = new ArrayList<>(shardVsReplicaCount.keySet());
|
|
||||||
int[] shardOrder = new int[shardNames.size()];
|
|
||||||
for (int i = 0; i < shardNames.size(); i++) shardOrder[i] = i;
|
|
||||||
|
|
||||||
boolean hasFuzzyRules = false;
|
|
||||||
int nonWildCardShardRules = 0;
|
|
||||||
for (Rule r : rules) {
|
|
||||||
if (r.isFuzzy()) hasFuzzyRules = true;
|
|
||||||
if (!r.shard.isWildCard()) {
|
|
||||||
nonWildCardShardRules++;
|
|
||||||
//we will have to try all combinations
|
|
||||||
if (shardNames.size() > 10) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
|
||||||
"Max 10 shards allowed if there is a non wild card shard specified in rule");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<ReplicaPosition, String> result = tryAllPermutations(shardNames, shardOrder, nonWildCardShardRules, false);
|
|
||||||
if (result == null && hasFuzzyRules) {
|
|
||||||
result = tryAllPermutations(shardNames, shardOrder, nonWildCardShardRules, true);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<ReplicaPosition, String> tryAllPermutations(List<String> shardNames,
|
|
||||||
int[] shardOrder,
|
|
||||||
int nonWildCardShardRules,
|
|
||||||
boolean fuzzyPhase) {
|
|
||||||
|
|
||||||
|
|
||||||
Iterator<int[]> shardPermutations = nonWildCardShardRules > 0 ?
|
|
||||||
permutations(shardNames.size()) :
|
|
||||||
singletonList(shardOrder).iterator();
|
|
||||||
|
|
||||||
for (; shardPermutations.hasNext(); ) {
|
|
||||||
int[] p = shardPermutations.next();
|
|
||||||
List<ReplicaPosition> replicaPositions = new ArrayList<>();
|
|
||||||
for (int pos : p) {
|
|
||||||
for (int j = 0; j < shardVsReplicaCount.get(shardNames.get(pos)); j++) {
|
|
||||||
replicaPositions.add(new ReplicaPosition(shardNames.get(pos), j, Replica.Type.NRT));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collections.sort(replicaPositions);
|
|
||||||
for (Iterator<int[]> it = permutations(rules.size()); it.hasNext(); ) {
|
|
||||||
int[] permutation = it.next();
|
|
||||||
Map<ReplicaPosition, String> result = tryAPermutationOfRules(permutation, replicaPositions, fuzzyPhase);
|
|
||||||
if (result != null) return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked"})
|
|
||||||
private Map<ReplicaPosition, String> tryAPermutationOfRules(int[] rulePermutation, List<ReplicaPosition> replicaPositions, boolean fuzzyPhase) {
|
|
||||||
Map<String, Map<String, Object>> nodeVsTagsCopy = getDeepCopy(nodeVsTags, 2);
|
|
||||||
Map<ReplicaPosition, String> result = new LinkedHashMap<>();
|
|
||||||
int startPosition = 0;
|
|
||||||
Map<String, Map<String, Integer>> copyOfCurrentState = getDeepCopy(shardVsNodes, 2);
|
|
||||||
List<String> sortedLiveNodes = new ArrayList<>(this.participatingLiveNodes);
|
|
||||||
Collections.sort(sortedLiveNodes, (String n1, String n2) -> {
|
|
||||||
int result1 = 0;
|
|
||||||
for (int i = 0; i < rulePermutation.length; i++) {
|
|
||||||
Rule rule = rules.get(rulePermutation[i]);
|
|
||||||
int val = rule.compare(n1, n2, nodeVsTagsCopy, copyOfCurrentState);
|
|
||||||
if (val != 0) {//atleast one non-zero compare break now
|
|
||||||
result1 = val;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (result1 == 0) {//if all else is equal, prefer nodes with fewer cores
|
|
||||||
AtomicInteger n1Count = nodeVsCores.get(n1);
|
|
||||||
AtomicInteger n2Count = nodeVsCores.get(n2);
|
|
||||||
int a = n1Count == null ? 0 : n1Count.get();
|
|
||||||
int b = n2Count == null ? 0 : n2Count.get();
|
|
||||||
result1 = a > b ? 1 : a == b ? 0 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return result1;
|
|
||||||
});
|
|
||||||
forEachPosition:
|
|
||||||
for (ReplicaPosition replicaPosition : replicaPositions) {
|
|
||||||
//trying to assign a node by verifying each rule in this rulePermutation
|
|
||||||
forEachNode:
|
|
||||||
for (int j = 0; j < sortedLiveNodes.size(); j++) {
|
|
||||||
String liveNode = sortedLiveNodes.get(startPosition % sortedLiveNodes.size());
|
|
||||||
startPosition++;
|
|
||||||
for (int i = 0; i < rulePermutation.length; i++) {
|
|
||||||
Rule rule = rules.get(rulePermutation[i]);
|
|
||||||
//trying to assign a replica into this node in this shard
|
|
||||||
Rule.MatchStatus status = rule.tryAssignNodeToShard(liveNode,
|
|
||||||
copyOfCurrentState, nodeVsTagsCopy, replicaPosition.shard, fuzzyPhase ? FUZZY_ASSIGN : ASSIGN);
|
|
||||||
if (status == Rule.MatchStatus.CANNOT_ASSIGN_FAIL) {
|
|
||||||
continue forEachNode;//try another node for this position
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//We have reached this far means this node can be applied to this position
|
|
||||||
//and all rules are fine. So let us change the currentState
|
|
||||||
result.put(replicaPosition, liveNode);
|
|
||||||
Map<String, Integer> nodeNames = copyOfCurrentState.get(replicaPosition.shard);
|
|
||||||
if (nodeNames == null) copyOfCurrentState.put(replicaPosition.shard, nodeNames = new HashMap<>());
|
|
||||||
Integer n = nodeNames.get(liveNode);
|
|
||||||
n = n == null ? 1 : n + 1;
|
|
||||||
nodeNames.put(liveNode, n);
|
|
||||||
Map<String, Object> tagsMap = nodeVsTagsCopy.get(liveNode);
|
|
||||||
Number coreCount = tagsMap == null ? null: (Number) tagsMap.get(ImplicitSnitch.CORES);
|
|
||||||
if (coreCount != null) {
|
|
||||||
nodeVsTagsCopy.get(liveNode).put(ImplicitSnitch.CORES, coreCount.intValue() + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue forEachPosition;
|
|
||||||
}
|
|
||||||
//if it reached here, we could not find a node for this position
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (replicaPositions.size() > result.size()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<ReplicaPosition, String> e : result.entrySet()) {
|
|
||||||
for (int i = 0; i < rulePermutation.length; i++) {
|
|
||||||
Rule rule = rules.get(rulePermutation[i]);
|
|
||||||
Rule.MatchStatus matchStatus = rule.tryAssignNodeToShard(e.getValue(),
|
|
||||||
copyOfCurrentState, nodeVsTagsCopy, e.getKey().shard, fuzzyPhase ? FUZZY_VERIFY : VERIFY);
|
|
||||||
if (matchStatus != NODE_CAN_BE_ASSIGNED && matchStatus != NOT_APPLICABLE) return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get all permutations for the int[] whose items are 0..level
|
|
||||||
*/
|
|
||||||
public static Iterator<int[]> permutations(final int level) {
|
|
||||||
return new Iterator<int[]>() {
|
|
||||||
int i = 0;
|
|
||||||
int[] next;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
AtomicReference<int[]> nthval = new AtomicReference<>();
|
|
||||||
permute(0, new int[level], new BitSet(level), nthval, i, new AtomicInteger());
|
|
||||||
i++;
|
|
||||||
next = nthval.get();
|
|
||||||
return next != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int[] next() {
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void permute(int level, int[] permuted, BitSet used, AtomicReference<int[]> nthval,
|
|
||||||
int requestedIdx, AtomicInteger seenSoFar) {
|
|
||||||
if (level == permuted.length) {
|
|
||||||
if (seenSoFar.get() == requestedIdx) nthval.set(permuted);
|
|
||||||
else seenSoFar.incrementAndGet();
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i < permuted.length; i++) {
|
|
||||||
if (!used.get(i)) {
|
|
||||||
used.set(i);
|
|
||||||
permuted[level] = i;
|
|
||||||
permute(level + 1, permuted, used, nthval, requestedIdx, seenSoFar);
|
|
||||||
if (nthval.get() != null) break;
|
|
||||||
used.set(i, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Map<String, SnitchContext> failedNodes = new HashMap<>();
|
|
||||||
|
|
||||||
static class SnitchInfoImpl extends SnitchContext.SnitchInfo {
|
|
||||||
final Snitch snitch;
|
|
||||||
final Set<String> myTags = new HashSet<>();
|
|
||||||
final Map<String, SnitchContext> nodeVsContext = new HashMap<>();
|
|
||||||
private final SolrCloudManager cloudManager;
|
|
||||||
|
|
||||||
SnitchInfoImpl(Map<String, Object> conf, Snitch snitch, SolrCloudManager cloudManager) {
|
|
||||||
super(conf);
|
|
||||||
this.snitch = snitch;
|
|
||||||
this.cloudManager = cloudManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getTagNames() {
|
|
||||||
return myTags;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method uses the snitches and get the tags for all the nodes
|
|
||||||
*/
|
|
||||||
@SuppressWarnings({"unchecked"})
|
|
||||||
private Map<String, Map<String, Object>> getTagsForNodes(final SolrCloudManager cloudManager, @SuppressWarnings({"rawtypes"})List snitchConf) {
|
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
Map<Class, SnitchInfoImpl> snitches = getSnitchInfos(cloudManager, snitchConf);
|
|
||||||
for (@SuppressWarnings({"rawtypes"})Class c : Snitch.WELL_KNOWN_SNITCHES) {
|
|
||||||
if (snitches.containsKey(c)) continue;// it is already specified explicitly , ignore
|
|
||||||
try {
|
|
||||||
snitches.put(c, new SnitchInfoImpl(Collections.EMPTY_MAP, (Snitch) c.getConstructor().newInstance(), cloudManager));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error instantiating Snitch " + c.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String tagName : tagNames) {
|
|
||||||
//identify which snitch is going to provide values for a given tag
|
|
||||||
boolean foundProvider = false;
|
|
||||||
for (SnitchInfoImpl info : snitches.values()) {
|
|
||||||
if (info.snitch.isKnownTag(tagName)) {
|
|
||||||
foundProvider = true;
|
|
||||||
info.myTags.add(tagName);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!foundProvider)
|
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown tag in rules " + tagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for (String node : participatingLiveNodes) {
|
|
||||||
//now use the Snitch to get the tags
|
|
||||||
for (SnitchInfoImpl info : snitches.values()) {
|
|
||||||
if (!info.myTags.isEmpty()) {
|
|
||||||
SnitchContext context = getSnitchCtx(node, info, cloudManager);
|
|
||||||
info.nodeVsContext.put(node, context);
|
|
||||||
try {
|
|
||||||
info.snitch.getTags(node, info.myTags, context);
|
|
||||||
} catch (Exception e) {
|
|
||||||
context.exception = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Map<String, Object>> result = new HashMap<>();
|
|
||||||
for (SnitchInfoImpl info : snitches.values()) {
|
|
||||||
for (Map.Entry<String, SnitchContext> e : info.nodeVsContext.entrySet()) {
|
|
||||||
SnitchContext context = e.getValue();
|
|
||||||
String node = e.getKey();
|
|
||||||
if (context.exception != null) {
|
|
||||||
failedNodes.put(node, context);
|
|
||||||
participatingLiveNodes.remove(node);
|
|
||||||
log.warn("Not all tags were obtained from node {}", node, context.exception);
|
|
||||||
context.exception = new SolrException(SolrException.ErrorCode.SERVER_ERROR,
|
|
||||||
"Not all tags were obtained from node " + node);
|
|
||||||
} else {
|
|
||||||
Map<String, Object> tags = result.get(node);
|
|
||||||
if (tags == null) {
|
|
||||||
tags = new HashMap<>();
|
|
||||||
result.put(node, tags);
|
|
||||||
}
|
|
||||||
tags.putAll(context.getTags());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (participatingLiveNodes.isEmpty()) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Could not get all tags for any nodes");
|
|
||||||
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> snitchSession = new HashMap<>();
|
|
||||||
|
|
||||||
protected SnitchContext getSnitchCtx(String node, SnitchInfoImpl info, SolrCloudManager cloudManager) {
|
|
||||||
return new ServerSnitchContext(info, node, snitchSession, cloudManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void verifySnitchConf(SolrCloudManager cloudManager, @SuppressWarnings({"rawtypes"})List snitchConf) {
|
|
||||||
getSnitchInfos(cloudManager, snitchConf);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
static Map<Class, SnitchInfoImpl> getSnitchInfos(SolrCloudManager cloudManager, List snitchConf) {
|
|
||||||
if (snitchConf == null) snitchConf = Collections.emptyList();
|
|
||||||
Map<Class, SnitchInfoImpl> snitches = new LinkedHashMap<>();
|
|
||||||
for (Object o : snitchConf) {
|
|
||||||
//instantiating explicitly specified snitches
|
|
||||||
String klas = null;
|
|
||||||
Map map = Collections.emptyMap();
|
|
||||||
if (o instanceof Map) {//it can be a Map
|
|
||||||
map = (Map) o;
|
|
||||||
klas = (String) map.get("class");
|
|
||||||
if (klas == null) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "snitch must have a class attribute");
|
|
||||||
}
|
|
||||||
} else { //or just the snitch name
|
|
||||||
klas = o.toString();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (klas.indexOf('.') == -1) klas = Snitch.class.getPackage().getName() + "." + klas;
|
|
||||||
Snitch inst =
|
|
||||||
(Snitch) Snitch.class.getClassLoader().loadClass(klas).getConstructor().newInstance() ;
|
|
||||||
snitches.put(inst.getClass(), new SnitchInfoImpl(map, inst, cloudManager));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return snitches;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,395 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.cloud.rule;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import org.apache.solr.common.SolrException;
|
|
||||||
import org.apache.solr.common.util.StrUtils;
|
|
||||||
import org.apache.solr.common.util.Utils;
|
|
||||||
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.MatchStatus.CANNOT_ASSIGN_FAIL;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.MatchStatus.NODE_CAN_BE_ASSIGNED;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.MatchStatus.NOT_APPLICABLE;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.Operand.EQUAL;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.Operand.GREATER_THAN;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.Operand.LESS_THAN;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.Operand.NOT_EQUAL;
|
|
||||||
import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_PROP;
|
|
||||||
import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
|
|
||||||
import static org.apache.solr.common.cloud.rule.ImplicitSnitch.CORES;
|
|
||||||
|
|
||||||
|
|
||||||
public class Rule {
|
|
||||||
public static final String WILD_CARD = "*";
|
|
||||||
public static final String WILD_WILD_CARD = "**";
|
|
||||||
static final Condition SHARD_DEFAULT = new Rule.Condition(SHARD_ID_PROP, WILD_WILD_CARD);
|
|
||||||
static final Condition REPLICA_DEFAULT = new Rule.Condition(REPLICA_PROP, WILD_CARD);
|
|
||||||
Condition shard;
|
|
||||||
Condition replica;
|
|
||||||
Condition tag;
|
|
||||||
|
|
||||||
public Rule(@SuppressWarnings({"rawtypes"})Map m) {
|
|
||||||
for (Object o : m.entrySet()) {
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
Map.Entry e = (Map.Entry) o;
|
|
||||||
Condition condition = new Condition(String.valueOf(e.getKey()), String.valueOf(e.getValue()));
|
|
||||||
if (condition.name.equals(SHARD_ID_PROP)) shard = condition;
|
|
||||||
else if (condition.name.equals(REPLICA_PROP)) replica = condition;
|
|
||||||
else {
|
|
||||||
if (tag != null) {
|
|
||||||
throw new RuntimeException("There can be only one and only one tag other than 'shard' and 'replica' in rule " + m);
|
|
||||||
}
|
|
||||||
tag = condition;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if (shard == null) shard = SHARD_DEFAULT;
|
|
||||||
if (replica == null) replica = REPLICA_DEFAULT;
|
|
||||||
if (tag == null) throw new RuntimeException("There should be a tag other than 'shard' and 'replica'");
|
|
||||||
if (replica.isWildCard() && tag.isWildCard()) {
|
|
||||||
throw new RuntimeException("Both replica and tag cannot be wild cards");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static Object parseObj(Object o, @SuppressWarnings({"rawtypes"})Class typ) {
|
|
||||||
if (o == null) return o;
|
|
||||||
if (typ == String.class) return String.valueOf(o);
|
|
||||||
if (typ == Integer.class) {
|
|
||||||
Double v = Double.parseDouble(String.valueOf(o));
|
|
||||||
return v.intValue();
|
|
||||||
}
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
public static Map parseRule(String s) {
|
|
||||||
Map<String, String> result = new LinkedHashMap<>();
|
|
||||||
s = s.trim();
|
|
||||||
List<String> keyVals = StrUtils.splitSmart(s, ',');
|
|
||||||
for (String kv : keyVals) {
|
|
||||||
List<String> keyVal = StrUtils.splitSmart(kv, ':');
|
|
||||||
if (keyVal.size() != 2) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid rule. should have only key and val in : " + kv);
|
|
||||||
}
|
|
||||||
if (keyVal.get(0).trim().length() == 0 || keyVal.get(1).trim().length() == 0) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid rule. should have key and val in : " + kv);
|
|
||||||
}
|
|
||||||
result.put(keyVal.get(0).trim(), keyVal.get(1).trim());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
public String toString() {
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
Map map = new LinkedHashMap();
|
|
||||||
if (shard != SHARD_DEFAULT) map.put(shard.name, shard.operand.toStr(shard.val));
|
|
||||||
if (replica != REPLICA_DEFAULT) map.put(replica.name, replica.operand.toStr(replica.val));
|
|
||||||
map.put(tag.name, tag.operand.toStr(tag.val));
|
|
||||||
return Utils.toJSONString(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if it is possible to assign this node as a replica of the given shard
|
|
||||||
* without violating this rule
|
|
||||||
*
|
|
||||||
* @param testNode The node in question
|
|
||||||
* @param shardVsNodeSet Set of nodes for every shard
|
|
||||||
* @param nodeVsTags The pre-fetched tags for all the nodes
|
|
||||||
* @param shardName The shard to which this node should be attempted
|
|
||||||
* @return MatchStatus
|
|
||||||
*/
|
|
||||||
MatchStatus tryAssignNodeToShard(String testNode,
|
|
||||||
Map<String, Map<String,Integer>> shardVsNodeSet,
|
|
||||||
Map<String, Map<String, Object>> nodeVsTags,
|
|
||||||
String shardName, Phase phase) {
|
|
||||||
|
|
||||||
if (tag.isWildCard()) {
|
|
||||||
//this is ensuring uniqueness across a certain tag
|
|
||||||
//eg: rack:r168
|
|
||||||
if (!shard.isWildCard() && shardName.equals(shard.val)) return NOT_APPLICABLE;
|
|
||||||
Object tagValueForThisNode = nodeVsTags.get(testNode).get(tag.name);
|
|
||||||
int v = getNumberOfNodesWithSameTagVal(shard, nodeVsTags, shardVsNodeSet,
|
|
||||||
shardName, new Condition(tag.name, tagValueForThisNode, EQUAL), phase);
|
|
||||||
if (phase == Phase.ASSIGN || phase == Phase.FUZZY_ASSIGN)
|
|
||||||
v++;//v++ because including this node , it becomes v+1 during ASSIGN
|
|
||||||
return replica.canMatch(v, phase) ?
|
|
||||||
NODE_CAN_BE_ASSIGNED :
|
|
||||||
CANNOT_ASSIGN_FAIL;
|
|
||||||
} else {
|
|
||||||
if (!shard.isWildCard() && !shardName.equals(shard.val)) return NOT_APPLICABLE;
|
|
||||||
if (replica.isWildCard()) {
|
|
||||||
//this means for each replica, the value must match
|
|
||||||
//shard match is already tested
|
|
||||||
Map<String, Object> tags = nodeVsTags.get(testNode);
|
|
||||||
if (tag.canMatch(tags == null ? null : tags.get(tag.name), phase)) return NODE_CAN_BE_ASSIGNED;
|
|
||||||
else return CANNOT_ASSIGN_FAIL;
|
|
||||||
} else {
|
|
||||||
int v = getNumberOfNodesWithSameTagVal(shard, nodeVsTags, shardVsNodeSet, shardName, tag, phase);
|
|
||||||
return replica.canMatch(v, phase) ? NODE_CAN_BE_ASSIGNED : CANNOT_ASSIGN_FAIL;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getNumberOfNodesWithSameTagVal(Condition shardCondition,
|
|
||||||
Map<String, Map<String, Object>> nodeVsTags,
|
|
||||||
Map<String, Map<String,Integer>> shardVsNodeSet,
|
|
||||||
String shardName,
|
|
||||||
Condition tagCondition,
|
|
||||||
Phase phase) {
|
|
||||||
|
|
||||||
int countMatchingThisTagValue = 0;
|
|
||||||
for (Map.Entry<String, Map<String,Integer>> entry : shardVsNodeSet.entrySet()) {
|
|
||||||
//check if this shard is relevant. either it is a ANY Wild card (**)
|
|
||||||
// or this shard is same as the shard in question
|
|
||||||
if (shardCondition.val.equals(WILD_WILD_CARD) || entry.getKey().equals(shardName)) {
|
|
||||||
Map<String,Integer> nodesInThisShard = shardVsNodeSet.get(shardCondition.val.equals(WILD_WILD_CARD) ? entry.getKey() : shardName);
|
|
||||||
if (nodesInThisShard != null) {
|
|
||||||
for (Map.Entry<String,Integer> aNode : nodesInThisShard.entrySet()) {
|
|
||||||
Map<String, Object> tagValues = nodeVsTags.get(aNode.getKey());
|
|
||||||
if(tagValues == null) continue;
|
|
||||||
Object obj = tagValues.get(tag.name);
|
|
||||||
if (tagCondition.canMatch(obj, phase)) countMatchingThisTagValue += aNode.getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return countMatchingThisTagValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int compare(String n1, String n2,
|
|
||||||
Map<String, Map<String, Object>> nodeVsTags,
|
|
||||||
Map<String, Map<String,Integer>> currentState) {
|
|
||||||
return tag.compare(n1, n2, nodeVsTags);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFuzzy() {
|
|
||||||
return shard.fuzzy || replica.fuzzy || tag.fuzzy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Operand {
|
|
||||||
EQUAL(""),
|
|
||||||
NOT_EQUAL("!") {
|
|
||||||
@Override
|
|
||||||
public boolean canMatch(Object ruleVal, Object testVal) {
|
|
||||||
return !super.canMatch(ruleVal, testVal);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GREATER_THAN(">") {
|
|
||||||
@Override
|
|
||||||
public Object match(String val) {
|
|
||||||
return checkNumeric(super.match(val));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canMatch(Object ruleVal, Object testVal) {
|
|
||||||
return testVal != null && compareNum(ruleVal, testVal) == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
LESS_THAN("<") {
|
|
||||||
@Override
|
|
||||||
public int compare(Object n1Val, Object n2Val) {
|
|
||||||
return GREATER_THAN.compare(n1Val, n2Val) * -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canMatch(Object ruleVal, Object testVal) {
|
|
||||||
return testVal != null && compareNum(ruleVal, testVal) == -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object match(String val) {
|
|
||||||
return checkNumeric(super.match(val));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
public final String operand;
|
|
||||||
|
|
||||||
Operand(String val) {
|
|
||||||
this.operand = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toStr(Object expectedVal) {
|
|
||||||
return operand + expectedVal.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
Object checkNumeric(Object val) {
|
|
||||||
if (val == null) return null;
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(val.toString());
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new RuntimeException("for operand " + operand + " the value must be numeric");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object match(String val) {
|
|
||||||
if (operand.isEmpty()) return val;
|
|
||||||
return val.startsWith(operand) ? val.substring(1) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean canMatch(Object ruleVal, Object testVal) {
|
|
||||||
return Objects.equals(String.valueOf(ruleVal), String.valueOf(testVal));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int compare(Object n1Val, Object n2Val) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int compareNum(Object n1Val, Object n2Val) {
|
|
||||||
Integer n1 = (Integer) parseObj(n1Val, Integer.class);
|
|
||||||
Integer n2 = (Integer) parseObj(n2Val, Integer.class);
|
|
||||||
return n1 > n2 ? -1 : Objects.equals(n1, n2) ? 0 : 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MatchStatus {
|
|
||||||
NODE_CAN_BE_ASSIGNED,
|
|
||||||
CANNOT_ASSIGN_GO_AHEAD,
|
|
||||||
NOT_APPLICABLE,
|
|
||||||
CANNOT_ASSIGN_FAIL
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Phase {
|
|
||||||
ASSIGN, VERIFY, FUZZY_ASSIGN, FUZZY_VERIFY
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Condition {
|
|
||||||
public final String name;
|
|
||||||
final Object val;
|
|
||||||
public final Operand operand;
|
|
||||||
final boolean fuzzy;
|
|
||||||
|
|
||||||
Condition(String name, Object val, Operand op) {
|
|
||||||
this.name = name;
|
|
||||||
this.val = val;
|
|
||||||
this.operand = op;
|
|
||||||
fuzzy = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Condition(String key, Object val) {
|
|
||||||
Object expectedVal;
|
|
||||||
boolean fuzzy = false;
|
|
||||||
if (val == null) throw new RuntimeException("value of a tag cannot be null for key " + key);
|
|
||||||
try {
|
|
||||||
this.name = key.trim();
|
|
||||||
String value = val.toString().trim();
|
|
||||||
if (value.endsWith("~")) {
|
|
||||||
fuzzy = true;
|
|
||||||
value = value.substring(0, value.length() - 1);
|
|
||||||
}
|
|
||||||
if ((expectedVal = NOT_EQUAL.match(value)) != null) {
|
|
||||||
operand = NOT_EQUAL;
|
|
||||||
} else if ((expectedVal = GREATER_THAN.match(value)) != null) {
|
|
||||||
operand = GREATER_THAN;
|
|
||||||
} else if ((expectedVal = LESS_THAN.match(value)) != null) {
|
|
||||||
operand = LESS_THAN;
|
|
||||||
} else {
|
|
||||||
operand = EQUAL;
|
|
||||||
expectedVal = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.equals(REPLICA_PROP)) {
|
|
||||||
if (!WILD_CARD.equals(expectedVal)) {
|
|
||||||
try {
|
|
||||||
expectedVal = Integer.parseInt(expectedVal.toString());
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new RuntimeException("The replica tag value can only be '*' or an integer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalArgumentException("Invalid condition : " + key + ":" + val, e);
|
|
||||||
}
|
|
||||||
this.val = expectedVal;
|
|
||||||
this.fuzzy = fuzzy;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isWildCard() {
|
|
||||||
return val.equals(WILD_CARD) || val.equals(WILD_WILD_CARD);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean canMatch(Object testVal, Phase phase) {
|
|
||||||
if (phase == Phase.FUZZY_ASSIGN || phase == Phase.FUZZY_VERIFY) return true;
|
|
||||||
if (phase == Phase.ASSIGN) {
|
|
||||||
if ((name.equals(REPLICA_PROP) || name.equals(CORES)) &&
|
|
||||||
(operand == GREATER_THAN || operand == NOT_EQUAL)) {
|
|
||||||
//the no:of replicas or cores will increase towards the end
|
|
||||||
//so this should only be checked in the Phase.
|
|
||||||
//process
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return operand.canMatch(val, testVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (obj instanceof Condition) {
|
|
||||||
Condition that = (Condition) obj;
|
|
||||||
return Objects.equals(name, that.name) &&
|
|
||||||
Objects.equals(operand, that.operand) &&
|
|
||||||
Objects.equals(val, that.val);
|
|
||||||
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(name, operand);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return name + ":" + operand.toStr(val) + (fuzzy ? "~" : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getInt() {
|
|
||||||
return (Integer) val;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int compare(String n1, String n2, Map<String, Map<String, Object>> nodeVsTags) {
|
|
||||||
Map<String, Object> tags = nodeVsTags.get(n1);
|
|
||||||
Object n1Val = tags == null ? null : tags.get(name);
|
|
||||||
tags = nodeVsTags.get(n2);
|
|
||||||
Object n2Val = tags == null ? null : tags.get(name);
|
|
||||||
if (n1Val == null || n2Val == null) return -1;
|
|
||||||
return isWildCard() ? 0 : operand.compare(n1Val, n2Val);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.cloud.rule;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
|
|
||||||
import org.apache.solr.common.cloud.rule.SnitchContext;
|
|
||||||
import org.apache.solr.common.util.Utils;
|
|
||||||
import org.apache.zookeeper.KeeperException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class ServerSnitchContext extends SnitchContext {
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
|
||||||
|
|
||||||
SolrCloudManager cloudManager;
|
|
||||||
public ServerSnitchContext(SnitchInfo perSnitch,
|
|
||||||
String node, Map<String, Object> session,
|
|
||||||
SolrCloudManager cloudManager) {
|
|
||||||
super(perSnitch, node, session);
|
|
||||||
this.cloudManager = cloudManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
public Map getZkJson(String path) throws KeeperException, InterruptedException {
|
|
||||||
try {
|
|
||||||
return Utils.getJson(cloudManager.getDistribStateManager(), path) ;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> getNodeValues(String node, Collection<String> tags){
|
|
||||||
return cloudManager.getNodeStateProvider().getNodeValues(node, tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Classes for managing Replica placement strategy when operating in <a href="http://wiki.apache.org/solr/SolrCloud">SolrCloud</a> mode.
|
|
||||||
*/
|
|
||||||
package org.apache.solr.cloud.rule;
|
|
||||||
|
|
||||||
|
|
|
@ -56,8 +56,6 @@ import org.apache.solr.cloud.ZkShardTerms;
|
||||||
import org.apache.solr.cloud.api.collections.ReindexCollectionCmd;
|
import org.apache.solr.cloud.api.collections.ReindexCollectionCmd;
|
||||||
import org.apache.solr.cloud.api.collections.RoutedAlias;
|
import org.apache.solr.cloud.api.collections.RoutedAlias;
|
||||||
import org.apache.solr.cloud.overseer.SliceMutator;
|
import org.apache.solr.cloud.overseer.SliceMutator;
|
||||||
import org.apache.solr.cloud.rule.ReplicaAssigner;
|
|
||||||
import org.apache.solr.cloud.rule.Rule;
|
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
import org.apache.solr.common.SolrException.ErrorCode;
|
import org.apache.solr.common.SolrException.ErrorCode;
|
||||||
import org.apache.solr.common.cloud.Aliases;
|
import org.apache.solr.common.cloud.Aliases;
|
||||||
|
@ -120,8 +118,6 @@ import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHan
|
||||||
import static org.apache.solr.cloud.api.collections.RoutedAlias.CREATE_COLLECTION_PREFIX;
|
import static org.apache.solr.cloud.api.collections.RoutedAlias.CREATE_COLLECTION_PREFIX;
|
||||||
import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
|
import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
|
||||||
import static org.apache.solr.common.cloud.DocCollection.DOC_ROUTER;
|
import static org.apache.solr.common.cloud.DocCollection.DOC_ROUTER;
|
||||||
import static org.apache.solr.common.cloud.DocCollection.RULE;
|
|
||||||
import static org.apache.solr.common.cloud.DocCollection.SNITCH;
|
|
||||||
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
|
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
|
||||||
import static org.apache.solr.common.cloud.ZkStateReader.NRT_REPLICAS;
|
import static org.apache.solr.common.cloud.ZkStateReader.NRT_REPLICAS;
|
||||||
import static org.apache.solr.common.cloud.ZkStateReader.PROPERTY_PROP;
|
import static org.apache.solr.common.cloud.ZkStateReader.PROPERTY_PROP;
|
||||||
|
@ -463,8 +459,6 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
|
||||||
CREATE_NODE_SET,
|
CREATE_NODE_SET,
|
||||||
CREATE_NODE_SET_SHUFFLE,
|
CREATE_NODE_SET_SHUFFLE,
|
||||||
SHARDS_PROP,
|
SHARDS_PROP,
|
||||||
RULE,
|
|
||||||
SNITCH,
|
|
||||||
PULL_REPLICAS,
|
PULL_REPLICAS,
|
||||||
TLOG_REPLICAS,
|
TLOG_REPLICAS,
|
||||||
NRT_REPLICAS,
|
NRT_REPLICAS,
|
||||||
|
@ -487,9 +481,6 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
|
||||||
props.put(REPLICATION_FACTOR, props.get(NRT_REPLICAS));
|
props.put(REPLICATION_FACTOR, props.get(NRT_REPLICAS));
|
||||||
}
|
}
|
||||||
|
|
||||||
addMapObject(props, RULE);
|
|
||||||
addMapObject(props, SNITCH);
|
|
||||||
verifyRuleParams(h.coreContainer, props);
|
|
||||||
final String collectionName = SolrIdentifierValidator.validateCollectionName((String) props.get(NAME));
|
final String collectionName = SolrIdentifierValidator.validateCollectionName((String) props.get(NAME));
|
||||||
final String shardsParam = (String) props.get(SHARDS_PROP);
|
final String shardsParam = (String) props.get(SHARDS_PROP);
|
||||||
if (StringUtils.isNotEmpty(shardsParam)) {
|
if (StringUtils.isNotEmpty(shardsParam)) {
|
||||||
|
@ -1036,8 +1027,6 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
|
||||||
formatString("no supported values provided {0}", CollectionAdminRequest.MODIFIABLE_COLLECTION_PROPERTIES.toString()));
|
formatString("no supported values provided {0}", CollectionAdminRequest.MODIFIABLE_COLLECTION_PROPERTIES.toString()));
|
||||||
}
|
}
|
||||||
copy(req.getParams().required(), m, COLLECTION_PROP);
|
copy(req.getParams().required(), m, COLLECTION_PROP);
|
||||||
addMapObject(m, RULE);
|
|
||||||
addMapObject(m, SNITCH);
|
|
||||||
for (Map.Entry<String, Object> entry : m.entrySet()) {
|
for (Map.Entry<String, Object> entry : m.entrySet()) {
|
||||||
String prop = entry.getKey();
|
String prop = entry.getKey();
|
||||||
if ("".equals(entry.getValue())) {
|
if ("".equals(entry.getValue())) {
|
||||||
|
@ -1046,7 +1035,6 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
|
||||||
}
|
}
|
||||||
DocCollection.verifyProp(m, prop);
|
DocCollection.verifyProp(m, prop);
|
||||||
}
|
}
|
||||||
verifyRuleParams(h.coreContainer, m);
|
|
||||||
if (m.get(REPLICATION_FACTOR) != null) {
|
if (m.get(REPLICATION_FACTOR) != null) {
|
||||||
m.put(NRT_REPLICAS, m.get(REPLICATION_FACTOR));
|
m.put(NRT_REPLICAS, m.get(REPLICATION_FACTOR));
|
||||||
}
|
}
|
||||||
|
@ -1435,44 +1423,6 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void verifyRuleParams(CoreContainer cc, Map<String, Object> m) {
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
List l = (List) m.get(RULE);
|
|
||||||
if (l != null) {
|
|
||||||
for (Object o : l) {
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
Map map = (Map) o;
|
|
||||||
try {
|
|
||||||
new Rule(map);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error in rule " + m, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cc != null && cc.isZooKeeperAware())
|
|
||||||
ReplicaAssigner.verifySnitchConf(cc.getZkController().getSolrCloudManager(), (List) m.get(SNITCH));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a String of the form a:b,c:d to a Map
|
|
||||||
*/
|
|
||||||
private static Map<String, Object> addMapObject(Map<String, Object> props, String key) {
|
|
||||||
Object v = props.get(key);
|
|
||||||
if (v == null) return props;
|
|
||||||
List<String> val = new ArrayList<>();
|
|
||||||
if (v instanceof String[]) {
|
|
||||||
val.addAll(Arrays.asList((String[]) v));
|
|
||||||
} else {
|
|
||||||
val.add(v.toString());
|
|
||||||
}
|
|
||||||
if (val.size() > 0) {
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
ArrayList<Map> l = new ArrayList<>();
|
|
||||||
for (String rule : val) l.add(Rule.parseRule(rule));
|
|
||||||
props.put(key, l);
|
|
||||||
}
|
|
||||||
return props;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void verifyShardsParam(String shardsParam) {
|
private static void verifyShardsParam(String shardsParam) {
|
||||||
for (String shard : shardsParam.split(",")) {
|
for (String shard : shardsParam.split(",")) {
|
||||||
|
|
|
@ -1,244 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.cloud.rule;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import org.apache.lucene.util.TestRuleLimitSysouts;
|
|
||||||
import org.apache.solr.SolrTestCaseJ4;
|
|
||||||
import org.apache.solr.common.SolrException;
|
|
||||||
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.zookeeper.KeeperException;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
|
|
||||||
import static org.hamcrest.core.Is.is;
|
|
||||||
import static org.mockito.Matchers.anyString;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
@TestRuleLimitSysouts.Limit(bytes=32000)
|
|
||||||
public class ImplicitSnitchTest extends SolrTestCaseJ4 {
|
|
||||||
|
|
||||||
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 beforeImplicitSnitchTest() {
|
|
||||||
snitch = new ImplicitSnitch();
|
|
||||||
context = new ServerSnitchContext(null, null, new HashMap<>(),null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetTags_withAllIPv4RequestedTags_with_omitted_zeros_returns_four_tags() throws Exception {
|
|
||||||
String node = "5:8983_solr";
|
|
||||||
|
|
||||||
snitch.getTags(node, Sets.newHashSet(IP_1, IP_2, IP_3, IP_4), context);
|
|
||||||
|
|
||||||
Map<String, Object> tags = context.getTags();
|
|
||||||
assertThat(tags.entrySet().size(), is(4));
|
|
||||||
assertThat(tags.get(IP_1), is("5"));
|
|
||||||
assertThat(tags.get(IP_2), is("0"));
|
|
||||||
assertThat(tags.get(IP_3), is("0"));
|
|
||||||
assertThat(tags.get(IP_4), is("0"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetTags_withAllIPv4RequestedTags_returns_four_tags() throws Exception {
|
|
||||||
String node = "192.168.1.2:8983_solr";
|
|
||||||
|
|
||||||
snitch.getTags(node, Sets.newHashSet(IP_1, IP_2, IP_3, IP_4), context);
|
|
||||||
|
|
||||||
Map<String, Object> tags = context.getTags();
|
|
||||||
assertThat(tags.entrySet().size(), is(4));
|
|
||||||
assertThat(tags.get(IP_1), is("2"));
|
|
||||||
assertThat(tags.get(IP_2), is("1"));
|
|
||||||
assertThat(tags.get(IP_3), is("168"));
|
|
||||||
assertThat(tags.get(IP_4), is("192"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetTags_withIPv4RequestedTags_ip2_and_ip4_returns_two_tags() throws Exception {
|
|
||||||
String node = "192.168.1.2:8983_solr";
|
|
||||||
|
|
||||||
SnitchContext context = new ServerSnitchContext(null, node, new HashMap<>(),null);
|
|
||||||
snitch.getTags(node, Sets.newHashSet(IP_2, IP_4), context);
|
|
||||||
|
|
||||||
Map<String, Object> tags = context.getTags();
|
|
||||||
assertThat(tags.entrySet().size(), is(2));
|
|
||||||
assertThat(tags.get(IP_2), is("1"));
|
|
||||||
assertThat(tags.get(IP_4), is("192"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore("SOLR-12360: local DNS resolver may return some address for a host named '192.168.1.2.1'")
|
|
||||||
public void testGetTags_with_wrong_ipv4_format_ip_returns_nothing() throws Exception {
|
|
||||||
String node = "192.168.1.2.1:8983_solr";
|
|
||||||
|
|
||||||
SnitchContext context = new ServerSnitchContext(null, node, new HashMap<>(),null);
|
|
||||||
snitch.getTags(node, Sets.newHashSet(IP_1), context);
|
|
||||||
|
|
||||||
Map<String, Object> tags = context.getTags();
|
|
||||||
assertThat(tags.entrySet().size(), is(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetTags_with_correct_ipv6_format_ip_returns_nothing() throws Exception {
|
|
||||||
String node = "[0:0:0:0:0:0:0:1]:8983_solr";
|
|
||||||
|
|
||||||
SnitchContext context = new ServerSnitchContext(null, node, new HashMap<>(),null);
|
|
||||||
snitch.getTags(node, Sets.newHashSet(IP_1), context);
|
|
||||||
|
|
||||||
Map<String, Object> tags = context.getTags();
|
|
||||||
assertThat(tags.entrySet().size(), is(0)); //This will fail when IPv6 is implemented
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetTags_withEmptyRequestedTag_returns_nothing() throws Exception {
|
|
||||||
String node = "192.168.1.2:8983_solr";
|
|
||||||
|
|
||||||
snitch.getTags(node, Sets.newHashSet(), context);
|
|
||||||
|
|
||||||
Map<String, Object> tags = context.getTags();
|
|
||||||
assertThat(tags.entrySet().size(), is(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetTags_withAllHostNameRequestedTags_returns_all_Tags() throws Exception {
|
|
||||||
SolrTestCaseJ4.assumeWorkingMockito();
|
|
||||||
|
|
||||||
String node = "serv01.dc01.london.uk.apache.org:8983_solr";
|
|
||||||
|
|
||||||
SnitchContext context = new ServerSnitchContext(null, node, new HashMap<>(),null);
|
|
||||||
//We need mocking here otherwise, we would need proper DNS entry for this test to pass
|
|
||||||
ImplicitSnitch mockedSnitch = Mockito.spy(snitch);
|
|
||||||
when(mockedSnitch.getHostIp(anyString())).thenReturn("10.11.12.13");
|
|
||||||
|
|
||||||
mockedSnitch.getTags(node, Sets.newHashSet(IP_1, IP_2, IP_3, IP_4), context);
|
|
||||||
|
|
||||||
Map<String, Object> tags = context.getTags();
|
|
||||||
assertThat(tags.entrySet().size(), is(4));
|
|
||||||
assertThat(tags.get(IP_1), is("13"));
|
|
||||||
assertThat(tags.get(IP_2), is("12"));
|
|
||||||
assertThat(tags.get(IP_3), is("11"));
|
|
||||||
assertThat(tags.get(IP_4), is("10"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetTags_withHostNameRequestedTag_ip3_returns_1_tag() throws Exception {
|
|
||||||
SolrTestCaseJ4.assumeWorkingMockito();
|
|
||||||
|
|
||||||
String node = "serv01.dc01.london.uk.apache.org:8983_solr";
|
|
||||||
|
|
||||||
SnitchContext context = new ServerSnitchContext(null, node, new HashMap<>(),null);
|
|
||||||
//We need mocking here otherwise, we would need proper DNS entry for this test to pass
|
|
||||||
ImplicitSnitch mockedSnitch = Mockito.spy(snitch);
|
|
||||||
when(mockedSnitch.getHostIp(anyString())).thenReturn("10.11.12.13");
|
|
||||||
mockedSnitch.getTags(node, Sets.newHashSet(IP_3), context);
|
|
||||||
|
|
||||||
Map<String, Object> tags = context.getTags();
|
|
||||||
assertThat(tags.entrySet().size(), is(1));
|
|
||||||
assertThat(tags.get(IP_3), is("11"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetTags_withHostNameRequestedTag_ip99999_returns_nothing() throws Exception {
|
|
||||||
SolrTestCaseJ4.assumeWorkingMockito();
|
|
||||||
|
|
||||||
String node = "serv01.dc01.london.uk.apache.org:8983_solr";
|
|
||||||
|
|
||||||
SnitchContext context = new ServerSnitchContext(null, node, new HashMap<>(),null);
|
|
||||||
//We need mocking here otherwise, we would need proper DNS entry for this test to pass
|
|
||||||
ImplicitSnitch mockedSnitch = Mockito.spy(snitch);
|
|
||||||
when(mockedSnitch.getHostIp(anyString())).thenReturn("10.11.12.13");
|
|
||||||
mockedSnitch.getTags(node, Sets.newHashSet("ip_99999"), context);
|
|
||||||
|
|
||||||
Map<String, Object> tags = context.getTags();
|
|
||||||
assertThat(tags.entrySet().size(), is(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIsKnownTag_ip1() throws Exception {
|
|
||||||
assertFalse(snitch.isKnownTag("ip_0"));
|
|
||||||
assertTrue(snitch.isKnownTag(IP_1));
|
|
||||||
assertTrue(snitch.isKnownTag(IP_2));
|
|
||||||
assertTrue(snitch.isKnownTag(IP_3));
|
|
||||||
assertTrue(snitch.isKnownTag(IP_4));
|
|
||||||
assertFalse(snitch.isKnownTag("ip_5"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testExceptions() throws Exception {
|
|
||||||
ImplicitSnitch implicitSnitch = new ImplicitSnitch();
|
|
||||||
ServerSnitchContext noNodeExceptionSnitch = new ServerSnitchContext(null, null, new HashMap<>(), null) {
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
public Map getZkJson(String path) throws KeeperException, InterruptedException {
|
|
||||||
throw new KeeperException.NoNodeException();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
implicitSnitch.getTags("", Collections.singleton(ImplicitSnitch.ROLE), noNodeExceptionSnitch);
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
Map map = (Map) noNodeExceptionSnitch.retrieve(ZkStateReader.ROLES); // todo it the key really supposed to /roles.json?
|
|
||||||
assertNotNull(map);
|
|
||||||
assertEquals(0, map.size());
|
|
||||||
|
|
||||||
implicitSnitch.getTags("", Collections.singleton(ImplicitSnitch.NODEROLE), noNodeExceptionSnitch);
|
|
||||||
map = (Map) noNodeExceptionSnitch.retrieve(ZkStateReader.ROLES); // todo it the key really supposed to /roles.json?
|
|
||||||
assertNotNull(map);
|
|
||||||
assertEquals(0, map.size());
|
|
||||||
|
|
||||||
ServerSnitchContext keeperExceptionSnitch = new ServerSnitchContext(null, null, new HashMap<>(), null) {
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
public Map getZkJson(String path) throws KeeperException, InterruptedException {
|
|
||||||
throw new KeeperException.ConnectionLossException();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
expectThrows(SolrException.class, KeeperException.ConnectionLossException.class, () -> implicitSnitch.getTags("", Collections.singleton(ImplicitSnitch.ROLE), keeperExceptionSnitch));
|
|
||||||
expectThrows(SolrException.class, KeeperException.ConnectionLossException.class, () -> implicitSnitch.getTags("", Collections.singleton(ImplicitSnitch.NODEROLE), keeperExceptionSnitch));
|
|
||||||
|
|
||||||
ServerSnitchContext remoteExceptionSnitch = new ServerSnitchContext(null, null, new HashMap<>(), null) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getNodeValues(String node, Collection<String> tags) {
|
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
expectThrows(SolrException.class, RuntimeException.class, () -> implicitSnitch.getTags("", Collections.singleton(ImplicitSnitch.CORES), remoteExceptionSnitch));
|
|
||||||
expectThrows(SolrException.class, RuntimeException.class, () -> implicitSnitch.getTags("", Collections.singleton(ImplicitSnitch.DISK), remoteExceptionSnitch));
|
|
||||||
expectThrows(SolrException.class, RuntimeException.class, () -> implicitSnitch.getTags("", Collections.singleton(ImplicitSnitch.SYSPROP + "xyz"), remoteExceptionSnitch));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,323 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.cloud.rule;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import org.apache.solr.SolrTestCaseJ4;
|
|
||||||
import org.apache.solr.client.solrj.cloud.DelegatingCloudManager;
|
|
||||||
import org.apache.solr.client.solrj.cloud.NodeStateProvider;
|
|
||||||
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
|
|
||||||
import org.apache.solr.common.cloud.Replica;
|
|
||||||
import org.apache.solr.common.cloud.ReplicaPosition;
|
|
||||||
import org.apache.solr.common.cloud.ZkStateReader;
|
|
||||||
import org.apache.solr.common.cloud.rule.Snitch;
|
|
||||||
import org.apache.solr.common.cloud.rule.SnitchContext;
|
|
||||||
import org.apache.solr.common.util.Utils;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static org.apache.solr.cloud.rule.Rule.parseRule;
|
|
||||||
import static org.apache.solr.common.util.Utils.makeMap;
|
|
||||||
|
|
||||||
public class RuleEngineTest extends SolrTestCaseJ4{
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
public void testPlacement2(){
|
|
||||||
|
|
||||||
|
|
||||||
String s = "{" +
|
|
||||||
" '127.0.0.1:49961_':{" +
|
|
||||||
" 'node':'127.0.0.1:49961_'," +
|
|
||||||
" 'freedisk':992," +
|
|
||||||
" 'cores':1}," +
|
|
||||||
" '127.0.0.1:49955_':{" +
|
|
||||||
" 'node':'127.0.0.1:49955_'," +
|
|
||||||
" 'freedisk':992," +
|
|
||||||
" 'cores':1}," +
|
|
||||||
" '127.0.0.1:49952_':{" +
|
|
||||||
" 'node':'127.0.0.1:49952_'," +
|
|
||||||
" 'freedisk':992," +
|
|
||||||
" 'cores':1}," +
|
|
||||||
" '127.0.0.1:49947_':{" +
|
|
||||||
" 'node':'127.0.0.1:49947_'," +
|
|
||||||
" 'freedisk':992," +
|
|
||||||
" 'cores':1}," +
|
|
||||||
" '127.0.0.1:49958_':{" +
|
|
||||||
" 'node':'127.0.0.1:49958_'," +
|
|
||||||
" 'freedisk':992," +
|
|
||||||
" 'cores':1}}";
|
|
||||||
MockSnitch.nodeVsTags = (Map) Utils.fromJSON(s.getBytes(StandardCharsets.UTF_8));
|
|
||||||
Map shardVsReplicaCount = makeMap("shard1", 2, "shard2", 2);
|
|
||||||
|
|
||||||
List<Rule> rules = parseRules("[{'cores':'<4'}, {" +
|
|
||||||
"'replica':'1',shard:'*','node':'*'}," +
|
|
||||||
" {'freedisk':'>1'}]");
|
|
||||||
|
|
||||||
Map<ReplicaPosition, String> mapping = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, singletonList(MockSnitch.class.getName()),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), null, null ).getNodeMappings();
|
|
||||||
assertNotNull(mapping);
|
|
||||||
|
|
||||||
mapping = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, singletonList(MockSnitch.class.getName()),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), null, null ).getNodeMappings();
|
|
||||||
assertNotNull(mapping);
|
|
||||||
|
|
||||||
rules = parseRules("[{role:'!overseer'}, {'freedisk':'>1'}]" );
|
|
||||||
Map<String, Object> snitchSession = new HashMap<>();
|
|
||||||
List<String> preferredOverseerNodes = ImmutableList.of("127.0.0.1:49947_", "127.0.0.1:49952_");
|
|
||||||
ReplicaAssigner replicaAssigner = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, singletonList(MockSnitch.class.getName()),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), null, null) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SnitchContext getSnitchCtx(String node, SnitchInfoImpl info, SolrCloudManager cloudManager) {
|
|
||||||
return new ServerSnitchContext(info, node, snitchSession,cloudManager){
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
public Map getZkJson(String path) {
|
|
||||||
if(ZkStateReader.ROLES.equals(path)){
|
|
||||||
return Collections.singletonMap("overseer", preferredOverseerNodes);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
mapping = replicaAssigner.getNodeMappings();
|
|
||||||
assertNotNull(mapping);
|
|
||||||
|
|
||||||
for (String nodeName : mapping.values()) {
|
|
||||||
assertFalse(preferredOverseerNodes.contains(nodeName));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
public void testPlacement3(){
|
|
||||||
String s = "{" +
|
|
||||||
" '127.0.0.1:49961_':{" +
|
|
||||||
" 'node':'127.0.0.1:49961_'," +
|
|
||||||
" 'freedisk':992," +
|
|
||||||
" 'cores':1}," +
|
|
||||||
" '127.0.0.2:49955_':{" +
|
|
||||||
" 'node':'127.0.0.1:49955_'," +
|
|
||||||
" 'freedisk':995," +
|
|
||||||
" 'cores':1}," +
|
|
||||||
" '127.0.0.3:49952_':{" +
|
|
||||||
" 'node':'127.0.0.1:49952_'," +
|
|
||||||
" 'freedisk':990," +
|
|
||||||
" 'cores':1}," +
|
|
||||||
" '127.0.0.1:49947_':{" +
|
|
||||||
" 'node':'127.0.0.1:49947_'," +
|
|
||||||
" 'freedisk':980," +
|
|
||||||
" 'cores':1}," +
|
|
||||||
" '127.0.0.2:49958_':{" +
|
|
||||||
" 'node':'127.0.0.1:49958_'," +
|
|
||||||
" 'freedisk':970," +
|
|
||||||
" 'cores':1}}";
|
|
||||||
MockSnitch.nodeVsTags = (Map) Utils.fromJSON(s.getBytes(StandardCharsets.UTF_8));
|
|
||||||
//test not
|
|
||||||
List<Rule> rules = parseRules(
|
|
||||||
"[{cores:'<4'}, " +
|
|
||||||
"{replica:'1',shard:'*',node:'*'}," +
|
|
||||||
"{node:'!127.0.0.1:49947_'}," +
|
|
||||||
"{freedisk:'>1'}]");
|
|
||||||
Map shardVsReplicaCount = makeMap("shard1", 2, "shard2", 2);
|
|
||||||
Map<ReplicaPosition, String> mapping = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, singletonList(MockSnitch.class.getName()),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), null, null).getNodeMappings();
|
|
||||||
assertNotNull(mapping);
|
|
||||||
assertFalse(mapping.containsValue("127.0.0.1:49947_"));
|
|
||||||
|
|
||||||
rules = parseRules(
|
|
||||||
"[{cores:'<4'}, " +
|
|
||||||
"{replica:'1',node:'*'}," +
|
|
||||||
"{freedisk:'>980'}]");
|
|
||||||
shardVsReplicaCount = makeMap("shard1", 2, "shard2", 2);
|
|
||||||
mapping = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, singletonList(MockSnitch.class.getName()),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), null, null).getNodeMappings0();
|
|
||||||
assertNull(mapping);
|
|
||||||
|
|
||||||
|
|
||||||
rules = parseRules(
|
|
||||||
"[{cores:'<4'}, " +
|
|
||||||
"{replica:'1',node:'*'}," +
|
|
||||||
"{freedisk:'>980~'}]");
|
|
||||||
shardVsReplicaCount = makeMap("shard1", 2, "shard2", 2);
|
|
||||||
mapping = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, singletonList(MockSnitch.class.getName()),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), null, null).getNodeMappings0();
|
|
||||||
assertNotNull(mapping);
|
|
||||||
assertFalse(mapping.containsValue("127.0.0.2:49958_"));
|
|
||||||
|
|
||||||
rules = parseRules(
|
|
||||||
"[{cores:'<4'}, " +
|
|
||||||
"{replica:'1',shard:'*',host:'*'}]"
|
|
||||||
);
|
|
||||||
shardVsReplicaCount = makeMap("shard1", 2, "shard2", 2);
|
|
||||||
mapping = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, singletonList(MockSnitch.class.getName()),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), null, null).getNodeMappings();
|
|
||||||
assertNotNull(mapping);
|
|
||||||
|
|
||||||
mapping = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, Collections.emptyList(),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), new DelegatingCloudManager(null){
|
|
||||||
@Override
|
|
||||||
public NodeStateProvider getNodeStateProvider() {
|
|
||||||
return new NodeStateProvider() {
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getNodeValues(String node, Collection<String> tags) {
|
|
||||||
return (Map<String, Object>) MockSnitch.nodeVsTags.get(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Map<String, List<Replica>>> getReplicaInfo(String node, Collection<String> keys) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, null).getNodeMappings();
|
|
||||||
assertNotNull(mapping);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
rules = parseRules(
|
|
||||||
"[{cores:'<4'}, " +
|
|
||||||
"{replica:'1',shard:'**',host:'*'}]"
|
|
||||||
);
|
|
||||||
shardVsReplicaCount = makeMap("shard1", 2, "shard2", 2);
|
|
||||||
mapping = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, singletonList(MockSnitch.class.getName()),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), null, null).getNodeMappings0();
|
|
||||||
assertNull(mapping);
|
|
||||||
|
|
||||||
rules = parseRules(
|
|
||||||
"[{cores:'<4'}, " +
|
|
||||||
"{replica:'1~',shard:'**',host:'*'}]"
|
|
||||||
);
|
|
||||||
shardVsReplicaCount = makeMap("shard1", 2, "shard2", 2);
|
|
||||||
mapping = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, singletonList(MockSnitch.class.getName()),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), null, null).getNodeMappings();
|
|
||||||
assertNotNull(mapping);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
private List<Rule> parseRules(String s) {
|
|
||||||
|
|
||||||
List maps = (List) Utils.fromJSON(s.getBytes(StandardCharsets.UTF_8));
|
|
||||||
|
|
||||||
List<Rule> rules = new ArrayList<>();
|
|
||||||
for (Object map : maps) rules.add(new Rule((Map) map));
|
|
||||||
return rules;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
public void testPlacement() throws Exception {
|
|
||||||
String rulesStr = "rack:*,replica:<2";
|
|
||||||
List<Rule> rules = parse(Arrays.asList(rulesStr));
|
|
||||||
Map shardVsReplicaCount = makeMap("shard1", 3, "shard2", 3);
|
|
||||||
Map nodeVsTags = makeMap(
|
|
||||||
"node1:80", makeMap("rack", "178"),
|
|
||||||
"node2:80", makeMap("rack", "179"),
|
|
||||||
"node3:80", makeMap("rack", "180"),
|
|
||||||
"node4:80", makeMap("rack", "181"),
|
|
||||||
"node5:80", makeMap("rack", "182")
|
|
||||||
);
|
|
||||||
MockSnitch.nodeVsTags = nodeVsTags;
|
|
||||||
Map<ReplicaPosition, String> mapping = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, singletonList(MockSnitch.class.getName()),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), null, null).getNodeMappings0();
|
|
||||||
assertNull(mapping);
|
|
||||||
rulesStr = "rack:*,replica:<2~";
|
|
||||||
rules = parse(Arrays.asList(rulesStr));
|
|
||||||
mapping = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, singletonList(MockSnitch.class.getName()),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), null ,null).getNodeMappings();
|
|
||||||
assertNotNull(mapping);
|
|
||||||
|
|
||||||
rulesStr = "rack:*,shard:*,replica:<2";//for each shard there can be a max of 1 replica
|
|
||||||
rules = parse(Arrays.asList(rulesStr));
|
|
||||||
mapping = new ReplicaAssigner(
|
|
||||||
rules,
|
|
||||||
shardVsReplicaCount, singletonList(MockSnitch.class.getName()),
|
|
||||||
new HashMap(), new ArrayList<>(MockSnitch.nodeVsTags.keySet()), null,null ).getNodeMappings();
|
|
||||||
assertNotNull(mapping);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MockSnitch extends Snitch {
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
static Map nodeVsTags = Collections.emptyMap();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings({"unchecked"})
|
|
||||||
public void getTags(String solrNode, Set<String> requestedTags, SnitchContext ctx) {
|
|
||||||
ctx.getTags().putAll((Map<? extends String, ?>) nodeVsTags.get(solrNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isKnownTag(String tag) {
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
Map next = (Map) nodeVsTags.values().iterator().next();
|
|
||||||
return next.containsKey(tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Rule> parse(List<String> rules) throws IOException {
|
|
||||||
assert rules != null && !rules.isEmpty();
|
|
||||||
ArrayList<Rule> result = new ArrayList<>();
|
|
||||||
for (String s : rules) {
|
|
||||||
if (s == null || s.trim().isEmpty()) continue;
|
|
||||||
result.add(new Rule(parseRule(s)));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,329 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.cloud.rule;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.apache.lucene.util.LuceneTestCase;
|
|
||||||
import org.apache.solr.client.solrj.SolrClient;
|
|
||||||
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
|
|
||||||
import org.apache.solr.client.solrj.impl.BaseHttpSolrClient;
|
|
||||||
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
|
|
||||||
import org.apache.solr.client.solrj.request.GenericSolrRequest;
|
|
||||||
import org.apache.solr.client.solrj.response.SimpleSolrResponse;
|
|
||||||
import org.apache.solr.cloud.SolrCloudTestCase;
|
|
||||||
import org.apache.solr.common.cloud.DocCollection;
|
|
||||||
import org.apache.solr.common.cloud.Replica;
|
|
||||||
import org.apache.solr.common.cloud.Slice;
|
|
||||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.rules.ExpectedException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
|
|
||||||
import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
|
|
||||||
import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PATH;
|
|
||||||
import static org.junit.matchers.JUnitMatchers.containsString;
|
|
||||||
|
|
||||||
@LuceneTestCase.Slow
|
|
||||||
public class RulesTest extends SolrCloudTestCase {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void setupCluster() throws Exception {
|
|
||||||
System.setProperty("metricsEnabled", "true");
|
|
||||||
configureCluster(5)
|
|
||||||
.addConfig("conf", configset("cloud-minimal"))
|
|
||||||
.configure();
|
|
||||||
}
|
|
||||||
|
|
||||||
@org.junit.Rule
|
|
||||||
public ExpectedException expectedException = ExpectedException.none();
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void removeCollections() throws Exception {
|
|
||||||
cluster.deleteAllCollections();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void doIntegrationTest() throws Exception {
|
|
||||||
assertEquals("Sanity Check: someone changed the cluster; " +
|
|
||||||
"test logic requires specific number of jetty nodes",
|
|
||||||
5, cluster.getJettySolrRunners().size());
|
|
||||||
|
|
||||||
final long minGB = (random().nextBoolean() ? 1 : 0);
|
|
||||||
final Path toTest = Paths.get("").toAbsolutePath();
|
|
||||||
assumeTrue("doIntegrationTest needs minGB="+minGB+" usable disk space",
|
|
||||||
ImplicitSnitch.getUsableSpaceInGB(toTest) > minGB);
|
|
||||||
|
|
||||||
String rulesColl = "rulesColl";
|
|
||||||
CollectionAdminRequest.createCollectionWithImplicitRouter(rulesColl, "conf", "shard1", 2)
|
|
||||||
.setRule("cores:<4", "node:*,replica:<2", "freedisk:>"+minGB)
|
|
||||||
.setSnitch("class:ImplicitSnitch")
|
|
||||||
.process(cluster.getSolrClient());
|
|
||||||
|
|
||||||
cluster.waitForActiveCollection(rulesColl, 1, 2);
|
|
||||||
|
|
||||||
DocCollection rulesCollection = getCollectionState(rulesColl);
|
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
List list = (List) rulesCollection.get("rule");
|
|
||||||
assertEquals(3, list.size());
|
|
||||||
assertEquals ( "<4", ((Map)list.get(0)).get("cores"));
|
|
||||||
assertEquals("<2", ((Map) list.get(1)).get("replica"));
|
|
||||||
assertEquals(">"+minGB, ((Map) list.get(2)).get("freedisk"));
|
|
||||||
list = (List) rulesCollection.get("snitch");
|
|
||||||
assertEquals(1, list.size());
|
|
||||||
assertEquals ( "ImplicitSnitch", ((Map)list.get(0)).get("class"));
|
|
||||||
|
|
||||||
CollectionAdminRequest.createShard(rulesColl, "shard2").process(cluster.getSolrClient());
|
|
||||||
CollectionAdminRequest.addReplicaToShard(rulesColl, "shard2").process(cluster.getSolrClient());
|
|
||||||
|
|
||||||
waitForState("Should have found shard1 w/2active replicas + shard2 w/1active replica",
|
|
||||||
rulesColl, (liveNodes, collection) -> {
|
|
||||||
// short circut if collection is deleted
|
|
||||||
// or we don't yet have the correct number of slices
|
|
||||||
if (null == collection || 2 != collection.getSlices().size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final Set<String> replicaNodes = new HashSet<>();
|
|
||||||
for (Slice slice : collection.getSlices()) {
|
|
||||||
// short circut if our slice isn't active
|
|
||||||
if (Slice.State.ACTIVE != slice.getState()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (slice.getName().equals("shard1")) {
|
|
||||||
// for shard1, we should have 2 fully live replicas
|
|
||||||
final List<Replica> liveReplicas = slice.getReplicas
|
|
||||||
((r) -> r.isActive(liveNodes));
|
|
||||||
if (2 != liveReplicas.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
replicaNodes.addAll(liveReplicas.stream().map
|
|
||||||
(Replica::getNodeName).collect(Collectors.toList()));
|
|
||||||
} else if (slice.getName().equals("shard2")) {
|
|
||||||
// for shard2, we should have 3 fully live replicas
|
|
||||||
final List<Replica> liveReplicas = slice.getReplicas
|
|
||||||
((r) -> r.isActive(liveNodes));
|
|
||||||
if (3 != liveReplicas.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
replicaNodes.addAll(liveReplicas.stream().map
|
|
||||||
(Replica::getNodeName).collect(Collectors.toList()));
|
|
||||||
} else {
|
|
||||||
// WTF?
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// now sanity check that the rules were *obeyed* and
|
|
||||||
// each replica is on a unique node
|
|
||||||
return 5 == replicaNodes.size();
|
|
||||||
});
|
|
||||||
|
|
||||||
// adding an additional replica should fail since our rule says at most one replica
|
|
||||||
// per node, and we know every node already has one replica
|
|
||||||
expectedException.expect(BaseHttpSolrClient.RemoteSolrException.class);
|
|
||||||
expectedException.expectMessage(containsString("Could not identify nodes matching the rules"));
|
|
||||||
CollectionAdminRequest.addReplicaToShard(rulesColl, "shard2").process(cluster.getSolrClient());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPortRule() throws Exception {
|
|
||||||
|
|
||||||
JettySolrRunner jetty = cluster.getRandomJetty(random());
|
|
||||||
String port = Integer.toString(jetty.getLocalPort());
|
|
||||||
|
|
||||||
String rulesColl = "portRuleColl";
|
|
||||||
CollectionAdminRequest.createCollectionWithImplicitRouter(rulesColl, "conf", "shard1", 2)
|
|
||||||
.setRule("port:" + port)
|
|
||||||
.setSnitch("class:ImplicitSnitch")
|
|
||||||
.process(cluster.getSolrClient());
|
|
||||||
|
|
||||||
waitForState("Collection should have followed port rule w/ImplicitSnitch, not cluster policy",
|
|
||||||
rulesColl, (liveNodes, rulesCollection) -> {
|
|
||||||
// first sanity check that the collection exists & the rules/snitch are listed
|
|
||||||
if (null == rulesCollection) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
List list = (List) rulesCollection.get("rule");
|
|
||||||
if (null == list || 1 != list.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (! port.equals(((Map) list.get(0)).get("port"))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
list = (List) rulesCollection.get("snitch");
|
|
||||||
if (null == list || 1 != list.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (! "ImplicitSnitch".equals(((Map)list.get(0)).get("class"))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (2 != rulesCollection.getReplicas().size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// now sanity check that the rules were *obeyed*
|
|
||||||
return rulesCollection.getReplicas().stream().allMatch
|
|
||||||
(replica -> (replica.getNodeName().contains(port) &&
|
|
||||||
replica.isActive(liveNodes)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings({"unchecked"})
|
|
||||||
public void testHostFragmentRule() throws Exception {
|
|
||||||
|
|
||||||
String rulesColl = "hostFragment";
|
|
||||||
|
|
||||||
JettySolrRunner jetty = cluster.getRandomJetty(random());
|
|
||||||
String host = jetty.getBaseUrl().getHost();
|
|
||||||
String[] ipFragments = host.split("\\.");
|
|
||||||
String ip_1 = ipFragments[ipFragments.length - 1];
|
|
||||||
String ip_2 = ipFragments[ipFragments.length - 2];
|
|
||||||
|
|
||||||
CollectionAdminRequest.createCollectionWithImplicitRouter(rulesColl, "conf", "shard1", 2)
|
|
||||||
.setRule("ip_2:" + ip_2, "ip_1:" + ip_1)
|
|
||||||
.setSnitch("class:ImplicitSnitch")
|
|
||||||
.process(cluster.getSolrClient());
|
|
||||||
|
|
||||||
cluster.waitForActiveCollection(rulesColl, 1, 2);
|
|
||||||
|
|
||||||
DocCollection rulesCollection = getCollectionState(rulesColl);
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
List<Map> list = (List<Map>) rulesCollection.get("rule");
|
|
||||||
assertEquals(2, list.size());
|
|
||||||
assertEquals(ip_2, list.get(0).get("ip_2"));
|
|
||||||
assertEquals(ip_1, list.get(1).get("ip_1"));
|
|
||||||
|
|
||||||
list = (List) rulesCollection.get("snitch");
|
|
||||||
assertEquals(1, list.size());
|
|
||||||
assertEquals("ImplicitSnitch", list.get(0).get("class"));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testHostFragmentRuleThrowsExceptionWhenIpDoesNotMatch() throws Exception {
|
|
||||||
|
|
||||||
String rulesColl = "ipRuleColl";
|
|
||||||
|
|
||||||
JettySolrRunner jetty = cluster.getRandomJetty(random());
|
|
||||||
String host = jetty.getBaseUrl().getHost();
|
|
||||||
String[] ipFragments = host.split("\\.");
|
|
||||||
String ip_1 = ipFragments[ipFragments.length - 1];
|
|
||||||
String ip_2 = ipFragments[ipFragments.length - 2];
|
|
||||||
|
|
||||||
expectedException.expect(BaseHttpSolrClient.RemoteSolrException.class);
|
|
||||||
expectedException.expectMessage(containsString("ip_1"));
|
|
||||||
|
|
||||||
CollectionAdminRequest.createCollectionWithImplicitRouter(rulesColl, "conf", "shard1", 2)
|
|
||||||
.setRule("ip_2:" + ip_2, "ip_1:" + ip_1 + "9999")
|
|
||||||
.setSnitch("class:ImplicitSnitch")
|
|
||||||
.process(cluster.getSolrClient());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInvokeApi() throws Exception {
|
|
||||||
JettySolrRunner jetty = cluster.getRandomJetty(random());
|
|
||||||
try (SolrClient client = getHttpSolrClient(jetty.getBaseUrl().toString())) {
|
|
||||||
GenericSolrRequest req = new GenericSolrRequest(GET, "/____v2/node/invoke", new ModifiableSolrParams()
|
|
||||||
.add("class", ImplicitSnitch.class.getName())
|
|
||||||
.add("cores", "1")
|
|
||||||
.add("freedisk", "1")
|
|
||||||
);
|
|
||||||
SimpleSolrResponse rsp = req.process(client);
|
|
||||||
assertNotNull(((Map) rsp.getResponse().get(ImplicitSnitch.class.getName())).get("cores"));
|
|
||||||
assertNotNull(((Map) rsp.getResponse().get(ImplicitSnitch.class.getName())).get("freedisk"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testModifyColl() throws Exception {
|
|
||||||
final Path toTest = Paths.get("").toAbsolutePath();
|
|
||||||
|
|
||||||
final long minGB1 = (random().nextBoolean() ? 1 : 0);
|
|
||||||
final long minGB2 = 5;
|
|
||||||
assumeTrue("testModifyColl needs minGB1="+minGB1+" usable disk space",
|
|
||||||
ImplicitSnitch.getUsableSpaceInGB(toTest) > minGB1);
|
|
||||||
assumeTrue("testModifyColl needs minGB2="+minGB2+" usable disk space",
|
|
||||||
ImplicitSnitch.getUsableSpaceInGB(toTest) > minGB2);
|
|
||||||
|
|
||||||
String rulesColl = "modifyColl";
|
|
||||||
CollectionAdminRequest.createCollection(rulesColl, "conf", 1, 2)
|
|
||||||
.setRule("cores:<4", "node:*,replica:1", "freedisk:>" + minGB1)
|
|
||||||
.setSnitch("class:ImplicitSnitch")
|
|
||||||
.process(cluster.getSolrClient());
|
|
||||||
|
|
||||||
cluster.waitForActiveCollection(rulesColl, 1, 2);
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: Make a MODIFYCOLLECTION SolrJ class
|
|
||||||
ModifiableSolrParams p = new ModifiableSolrParams();
|
|
||||||
p.add("collection", rulesColl);
|
|
||||||
p.add("action", "MODIFYCOLLECTION");
|
|
||||||
p.add("rule", "cores:<5");
|
|
||||||
p.add("rule", "node:*,replica:1");
|
|
||||||
p.add("rule", "freedisk:>"+minGB2);
|
|
||||||
cluster.getSolrClient().request(new GenericSolrRequest(POST, COLLECTIONS_HANDLER_PATH, p));
|
|
||||||
|
|
||||||
waitForState("Should have found updated rules in DocCollection",
|
|
||||||
rulesColl, (liveNodes, rulesCollection) -> {
|
|
||||||
if (null == rulesCollection) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@SuppressWarnings({"rawtypes"})
|
|
||||||
List list = (List) rulesCollection.get("rule");
|
|
||||||
if (null == list || 3 != list.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (! "<5".equals(((Map) list.get(0)).get("cores"))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (! "1".equals(((Map) list.get(1)).get("replica"))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (! (">"+minGB2).equals(((Map) list.get(2)).get("freedisk"))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
list = (List) rulesCollection.get("snitch");
|
|
||||||
if (null == list || 1 != list.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (! "ImplicitSnitch".equals(((Map) list.get(0)).get("class"))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -173,10 +173,6 @@ public class TestCollectionAPIs extends SolrTestCaseJ4 {
|
||||||
"{collection: collName, shard: shard1, replica : replica1 , property : propA , operation : deletereplicaprop}"
|
"{collection: collName, shard: shard1, replica : replica1 , property : propA , operation : deletereplicaprop}"
|
||||||
);
|
);
|
||||||
|
|
||||||
compareOutput(apiBag, "/collections/collName", POST,
|
|
||||||
"{modify : {rule : ['replica:*, cores:<5']} }", null,
|
|
||||||
"{collection: collName, operation : modifycollection , rule : [{replica: '*', cores : '<5' }]}"
|
|
||||||
);
|
|
||||||
compareOutput(apiBag, "/cluster", POST,
|
compareOutput(apiBag, "/cluster", POST,
|
||||||
"{add-role : {role : overseer, node : 'localhost_8978'} }", null,
|
"{add-role : {role : overseer, node : 'localhost_8978'} }", null,
|
||||||
"{operation : addrole ,role : overseer, node : 'localhost_8978'}"
|
"{operation : addrole ,role : overseer, node : 'localhost_8978'}"
|
||||||
|
|
|
@ -542,18 +542,13 @@ This has been observed during collection creation perf testing at Salesforce (no
|
||||||
|
|
||||||
buildReplicaPositions() gets the list of nodes to which replicas should be assigned by calling `Assign.getLiveOrLiveAndCreateNodeSetList()` (method name is because either all live nodes are used or only those present in message parameter `CREATE_NODE_SET`). It then verifies that there are enough nodes to place the replicas on and delegates to an `Assign.AssignStrategy` the computation of the actual replica distribution.
|
buildReplicaPositions() gets the list of nodes to which replicas should be assigned by calling `Assign.getLiveOrLiveAndCreateNodeSetList()` (method name is because either all live nodes are used or only those present in message parameter `CREATE_NODE_SET`). It then verifies that there are enough nodes to place the replicas on and delegates to an `Assign.AssignStrategy` the computation of the actual replica distribution.
|
||||||
|
|
||||||
There are 3 implementations of `AssignStrategy`: `LegacyAssignStrategy`, `RulesBasedAssignStrategy` and `PolicyBasedAssignStrategy`.
|
There is a default implementation of `AssignStrategy`: `LegacyAssignStrategy`
|
||||||
|
|
||||||
The one in use in modern configurations is `PolicyBasedAssignStrategy`. It is unclear to me when `LegacyAssignStrategy` is used.
|
|
||||||
|
|
||||||
For a new collection (having no existing replicas), `LegacyAssignStrategy` will round robin over all nodes, sorted by increasing number of cores they already host. A collection with few replicas (compared to the number nodes) will be placed on the nodes having less cores at the time the computation is made. Larger collections will basically be distributed equally over the cluster, regardless of the number of cores already present on each node (since the round robin will assign multiple replicas to each node, the iteration order doesn’t make a big difference). +
|
For a new collection (having no existing replicas), `LegacyAssignStrategy` will round robin over all nodes, sorted by increasing number of cores they already host. A collection with few replicas (compared to the number nodes) will be placed on the nodes having less cores at the time the computation is made. Larger collections will basically be distributed equally over the cluster, regardless of the number of cores already present on each node (since the round robin will assign multiple replicas to each node, the iteration order doesn’t make a big difference). +
|
||||||
Note when `LegacyAssignStrategy` is assigning replicas for a collection that already exists in the cluster, each core (replica) of that collection on a node counts as 100 cores of other replicas, nodes are ordered by increasing count of cores (and 100x that collection cores) but nodes already having the maximum number of shards of that collection are excluded from the set of candidate nodes for new assignments. Nodes not having reached the max number of cores for that collection might get replica assignments that make them exceed that maximum and exceed the number of cores on the nodes initially excluded, that should instead have been assigned a few cores as well… +
|
Note when `LegacyAssignStrategy` is assigning replicas for a collection that already exists in the cluster, each core (replica) of that collection on a node counts as 100 cores of other replicas, nodes are ordered by increasing count of cores (and 100x that collection cores) but nodes already having the maximum number of shards of that collection are excluded from the set of candidate nodes for new assignments. Nodes not having reached the max number of cores for that collection might get replica assignments that make them exceed that maximum and exceed the number of cores on the nodes initially excluded, that should instead have been assigned a few cores as well… +
|
||||||
Given “legacy” in the name, I assume that doesn’t really matter.
|
Given “legacy” in the name, I assume that doesn’t really matter.
|
||||||
|
|
||||||
With `RulesBasedAssignStrategy`, only NRT replicas can be placed (TLOG and PULL not supported). After some initial set up, the work is delegated to `org.apache.solr.cloud.rule.ReplicaAssigner`. Given that’s not the way things should be done going forward, I will not try to understand how it works.
|
|
||||||
|
|
||||||
The modern way to do things is `PolicyBasedAssignStrategy` that will keep its secrets from me for now… Describing the Autoscaling framework might be added later to this doc in a totally separate chapter but I don’t know much about it at this point. +
|
|
||||||
See https://issues.apache.org/jira/browse/SOLR-14275[SOLR-14275] “Policy calculations are very slow for large clusters and large operations” for ongoing work.
|
|
||||||
|
|
||||||
[[ZkStateWriter,ZkStateWriter]]
|
[[ZkStateWriter,ZkStateWriter]]
|
||||||
=== org.apache.solr.cloud.overseer.ZkStateWriter
|
=== org.apache.solr.cloud.overseer.ZkStateWriter
|
||||||
|
|
|
@ -93,12 +93,6 @@ Set core property _name_ to _value_. See the section <<defining-core-properties.
|
||||||
`async`::
|
`async`::
|
||||||
Request ID to track this action which will be <<collections-api.adoc#asynchronous-calls,processed asynchronously>>.
|
Request ID to track this action which will be <<collections-api.adoc#asynchronous-calls,processed asynchronously>>.
|
||||||
|
|
||||||
`rule`::
|
|
||||||
Replica placement rules. See the section <<rule-based-replica-placement.adoc#rule-based-replica-placement,Rule-based Replica Placement>> for details.
|
|
||||||
|
|
||||||
`snitch`::
|
|
||||||
Details of the snitch provider. See the section <<rule-based-replica-placement.adoc#rule-based-replica-placement,Rule-based Replica Placement>> for details.
|
|
||||||
|
|
||||||
`waitForFinalState`::
|
`waitForFinalState`::
|
||||||
If `true`, the request will complete only when all affected replicas become active. The default is `false`, which means that the API will return the status of the single action, which may be before the new replica is online and active.
|
If `true`, the request will complete only when all affected replicas become active. The default is `false`, which means that the API will return the status of the single action, which may be before the new replica is online and active.
|
||||||
|
|
||||||
|
|
|
@ -1,177 +0,0 @@
|
||||||
= Rule-based Replica Placement
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
When Solr needs to assign nodes to collections, it can either automatically assign them randomly or the user can specify a set of nodes where it should create the replicas.
|
|
||||||
|
|
||||||
With very large clusters, it is hard to specify exact node names and it still does not give you fine grained control over how nodes are chosen for a shard. The user should be in complete control of where the nodes are allocated for each collection, shard and replica. This helps to optimally allocate hardware resources across the cluster.
|
|
||||||
|
|
||||||
Rule-based replica assignment allows the creation of rules to determine the placement of replicas in the cluster. In the future, this feature will help to automatically add or remove replicas when systems go down, or when higher throughput is required. This enables a more hands-off approach to administration of the cluster.
|
|
||||||
|
|
||||||
This feature is used in the following instances:
|
|
||||||
|
|
||||||
* Collection creation
|
|
||||||
* Shard creation
|
|
||||||
* Replica creation
|
|
||||||
* Shard splitting
|
|
||||||
|
|
||||||
== Common Use Cases
|
|
||||||
|
|
||||||
There are several situations where this functionality may be used. A few of the rules that could be implemented are listed below:
|
|
||||||
|
|
||||||
* Don’t assign more than 1 replica of this collection to a host.
|
|
||||||
* Assign all replicas to nodes with more than 100GB of free disk space or, assign replicas where there is more disk space.
|
|
||||||
* Do not assign any replica on a given host because I want to run an overseer there.
|
|
||||||
* Assign only one replica of a shard in a rack.
|
|
||||||
* Assign replica in nodes hosting less than 5 cores.
|
|
||||||
* Assign replicas in nodes hosting the least number of cores.
|
|
||||||
|
|
||||||
== Rule Conditions
|
|
||||||
|
|
||||||
A rule is a set of conditions that a node must satisfy before a replica core can be created there.
|
|
||||||
|
|
||||||
There are three possible conditions.
|
|
||||||
|
|
||||||
* *shard*: this is the name of a shard or a wild card (* means for all shards). If shard is not specified, then the rule applies to the entire collection.
|
|
||||||
* *replica*: this can be a number or a wild-card (* means any number zero to infinity).
|
|
||||||
* *tag*: this is an attribute of a node in the cluster that can be used in a rule, e.g., “freedisk”, “cores”, “rack”, “dc”, etc. The tag name can be a custom string. If creating a custom tag, a snitch is responsible for providing tags and values. The section <<Snitches>> below describes how to add a custom tag, and defines six pre-defined tags (cores, freedisk, host, port, node, and sysprop).
|
|
||||||
|
|
||||||
=== Rule Operators
|
|
||||||
|
|
||||||
A condition can have one of the following operators to set the parameters for the rule.
|
|
||||||
|
|
||||||
* *equals (no operator required)*: `tag:x` means tag value must be equal to ‘x’
|
|
||||||
* *greater than (>)*: `tag:>x` means tag value greater than ‘x’. x must be a number
|
|
||||||
* *less than (<)*: `tag:<x` means tag value less than ‘x’. x must be a number
|
|
||||||
* *not equal (!)*: `tag:!x` means tag value MUST NOT be equal to ‘x’. The equals check is performed on String value
|
|
||||||
|
|
||||||
=== Fuzzy Operator (~)
|
|
||||||
|
|
||||||
This can be used as a suffix to any condition. This would first try to satisfy the rule strictly. If Solr can’t find enough nodes to match the criterion, it tries to find the next best match which may not satisfy the criterion. For example, if we have a rule such as, `freedisk:>200~`, Solr will try to assign replicas of this collection on nodes with more than 200GB of free disk space. If that is not possible, the node which has the most free disk space will be chosen instead.
|
|
||||||
|
|
||||||
=== Choosing Among Equals
|
|
||||||
|
|
||||||
The nodes are sorted first and the rules are used to sort them. This ensures that even if many nodes match the rules, the best nodes are picked up for node assignment. For example, if there is a rule such as `freedisk:>20`, nodes are sorted first on disk space descending and the node with the most disk space is picked up first. Or, if the rule is `cores:<5`, nodes are sorted with number of cores ascending and the node with the least number of cores is picked up first.
|
|
||||||
|
|
||||||
== Rules for New Shards
|
|
||||||
|
|
||||||
The rules are persisted along with collection state. So, when a new replica is created, the system will assign replicas satisfying the rules. When a new shard is created as a result of using the Collection API's <<shard-management.adoc#createshard,CREATESHARD command>>, ensure that you have created rules specific for that shard name. Rules can be altered using the <<collection-management.adoc#modifycollection,MODIFYCOLLECTION command>>. However, it is not required to do so if the rules do not specify explicit shard names. For example, a rule such as `shard:shard1,replica:*,ip_3:168:`, will not apply to any new shard created. But, if your rule is `replica:*,ip_3:168`, then it will apply to any new shard created.
|
|
||||||
|
|
||||||
The same is applicable to shard splitting. Shard splitting is treated exactly the same way as shard creation. Even though `shard1_1` and `shard1_2` may be created from `shard1`, the rules treat them as distinct, unrelated shards.
|
|
||||||
|
|
||||||
== Snitches
|
|
||||||
|
|
||||||
Tag values come from a plugin called Snitch. If there is a tag named ‘rack’ in a rule, there must be Snitch which provides the value for ‘rack’ for each node in the cluster. A snitch implements the Snitch interface. Solr, by default, provides a default snitch which provides the following tags:
|
|
||||||
|
|
||||||
* *cores*: Number of cores in the node
|
|
||||||
* *freedisk*: Disk space available in the node
|
|
||||||
* *host*: host name of the node
|
|
||||||
* *port*: port of the node
|
|
||||||
* *node*: node name
|
|
||||||
* *role*: The role of the node. The only supported role is 'overseer'
|
|
||||||
* *ip_1, ip_2, ip_3, ip_4*: These are ip fragments for each node. For example, in a host with ip `192.168.1.2`, `ip_1 = 2`, `ip_2 =1`, `ip_3 = 168` and` ip_4 = 192`
|
|
||||||
* *sysprop.\{PROPERTY_NAME}*: These are values available from system properties. `sysprop.key` means a value that is passed to the node as `-Dkey=keyValue` during the node startup. It is possible to use rules like `sysprop.key:expectedVal,shard:*`
|
|
||||||
|
|
||||||
=== How Snitches are Configured
|
|
||||||
|
|
||||||
It is possible to use one or more snitches for a set of rules. If the rules only need tags from default snitch it need not be explicitly configured. For example:
|
|
||||||
|
|
||||||
[source,text]
|
|
||||||
----
|
|
||||||
snitch=class:fqn.ClassName,key1:val1,key2:val2,key3:val3
|
|
||||||
----
|
|
||||||
|
|
||||||
*How Tag Values are Collected*
|
|
||||||
|
|
||||||
. Identify the set of tags in the rules
|
|
||||||
. Create instances of Snitches specified. The default snitch is always created.
|
|
||||||
. Ask each Snitch if it can provide values for the any of the tags. If even one tag does not have a snitch, the assignment fails.
|
|
||||||
. After identifying the Snitches, they provide the tag values for each node in the cluster.
|
|
||||||
. If the value for a tag is not obtained for a given node, it cannot participate in the assignment.
|
|
||||||
|
|
||||||
== Replica Placement Examples
|
|
||||||
|
|
||||||
=== Keep less than 2 replicas (at most 1 replica) of this collection on any node
|
|
||||||
|
|
||||||
For this rule, we define the `replica` condition with operators for "less than 2", and use a pre-defined tag named `node` to define nodes with any name.
|
|
||||||
|
|
||||||
[source,text]
|
|
||||||
----
|
|
||||||
replica:<2,node:*
|
|
||||||
// this is equivalent to replica:<2,node:*,shard:**. We can omit shard:** because ** is the default value of shard
|
|
||||||
----
|
|
||||||
|
|
||||||
=== For a given shard, keep less than 2 replicas on any node
|
|
||||||
|
|
||||||
For this rule, we use the `shard` condition to define any shard, the `replica` condition with operators for "less than 2", and finally a pre-defined tag named `node` to define nodes with any name.
|
|
||||||
|
|
||||||
[source,text]
|
|
||||||
----
|
|
||||||
shard:*,replica:<2,node:*
|
|
||||||
----
|
|
||||||
|
|
||||||
=== Assign all replicas in shard1 to rack 730
|
|
||||||
|
|
||||||
This rule limits the `shard` condition to 'shard1', but any number of replicas. We're also referencing a custom tag named `rack`. Before defining this rule, we will need to configure a custom Snitch which provides values for the tag `rack`.
|
|
||||||
|
|
||||||
[source,text]
|
|
||||||
----
|
|
||||||
shard:shard1,replica:*,rack:730
|
|
||||||
----
|
|
||||||
|
|
||||||
In this case, the default value of `replica` is `*`, or all replicas. It can be omitted and the rule will be reduced to:
|
|
||||||
|
|
||||||
[source,text]
|
|
||||||
----
|
|
||||||
shard:shard1,rack:730
|
|
||||||
----
|
|
||||||
|
|
||||||
=== Create replicas in nodes with less than 5 cores only
|
|
||||||
|
|
||||||
This rule uses the `replica` condition to define any number of replicas, but adds a pre-defined tag named `core` and uses operators for "less than 5".
|
|
||||||
|
|
||||||
[source,text]
|
|
||||||
----
|
|
||||||
replica:*,cores:<5
|
|
||||||
----
|
|
||||||
|
|
||||||
Again, we can simplify this to use the default value for `replica`, like so:
|
|
||||||
|
|
||||||
[source,text]
|
|
||||||
----
|
|
||||||
cores:<5
|
|
||||||
----
|
|
||||||
|
|
||||||
=== Do not create any replicas in host 192.45.67.3
|
|
||||||
|
|
||||||
This rule uses only the pre-defined tag `host` to define an IP address where replicas should not be placed.
|
|
||||||
|
|
||||||
[source,text]
|
|
||||||
----
|
|
||||||
host:!192.45.67.3
|
|
||||||
----
|
|
||||||
|
|
||||||
== Defining Rules
|
|
||||||
|
|
||||||
Rules are specified per collection during collection creation as request parameters. It is possible to specify multiple ‘rule’ and ‘snitch’ parameters as in this example:
|
|
||||||
|
|
||||||
[source,text]
|
|
||||||
----
|
|
||||||
snitch=class:EC2Snitch&rule=shard:*,replica:1,dc:dc1&rule=shard:*,replica:<2,dc:dc3
|
|
||||||
----
|
|
||||||
|
|
||||||
These rules are persisted in the collection's `state.json` in ZooKeeper and are available throughout the lifetime of the collection. This enables the system to perform any future node allocation without direct user interaction. The rules added during collection creation can be modified later using the <<collection-management.adoc#modifycollection,MODIFYCOLLECTION>> API.
|
|
|
@ -1,5 +1,5 @@
|
||||||
= SolrCloud
|
= SolrCloud
|
||||||
:page-children: getting-started-with-solrcloud, how-solrcloud-works, solrcloud-resilience, solrcloud-configuration-and-parameters, rule-based-replica-placement
|
:page-children: getting-started-with-solrcloud, how-solrcloud-works, solrcloud-resilience, solrcloud-configuration-and-parameters
|
||||||
// Licensed to the Apache Software Foundation (ASF) under one
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
// or more contributor license agreements. See the NOTICE file
|
// or more contributor license agreements. See the NOTICE file
|
||||||
// distributed with this work for additional information
|
// distributed with this work for additional information
|
||||||
|
@ -43,4 +43,3 @@ In this section, we'll cover everything you need to know about using Solr in Sol
|
||||||
** <<command-line-utilities.adoc#command-line-utilities,Command Line Utilities>>
|
** <<command-line-utilities.adoc#command-line-utilities,Command Line Utilities>>
|
||||||
** <<solrcloud-with-legacy-configuration-files.adoc#solrcloud-with-legacy-configuration-files,SolrCloud with Legacy Configuration Files>>
|
** <<solrcloud-with-legacy-configuration-files.adoc#solrcloud-with-legacy-configuration-files,SolrCloud with Legacy Configuration Files>>
|
||||||
** <<configsets-api.adoc#configsets-api,Configsets API>>
|
** <<configsets-api.adoc#configsets-api,Configsets API>>
|
||||||
* <<rule-based-replica-placement.adoc#rule-based-replica-placement,Rule-based Replica Placement>>
|
|
|
@ -116,16 +116,6 @@ Example of introspect for a POST API: `\http://localhost:8983/api/c/gettingstart
|
||||||
"description":"Modifies specific attributes of a collection. Multiple attributes can be changed at one time.",
|
"description":"Modifies specific attributes of a collection. Multiple attributes can be changed at one time.",
|
||||||
"type":"object",
|
"type":"object",
|
||||||
"properties":{
|
"properties":{
|
||||||
"rule":{
|
|
||||||
"type":"array",
|
|
||||||
"documentation":"https://lucene.apache.org/solr/guide/rule-based-replica-placement.html",
|
|
||||||
"description":"Modifies the rules for where replicas should be located in a cluster.",
|
|
||||||
"items":{"type":"string"}},
|
|
||||||
"snitch":{
|
|
||||||
"type":"array",
|
|
||||||
"documentation":"https://lucene.apache.org/solr/guide/rule-based-replica-placement.html",
|
|
||||||
"description":"Details of the snitch provider",
|
|
||||||
"items":{"type":"string"}},
|
|
||||||
"replicationFactor":{
|
"replicationFactor":{
|
||||||
"type":"string",
|
"type":"string",
|
||||||
"description":"The number of replicas to be created for each shard. Replicas are physical copies of each shard, acting as failover for the shard. Note that changing this value on an existing collection does not automatically add more replicas to the collection. However, it will allow add-replica commands to succeed."}}}}}],
|
"description":"The number of replicas to be created for each shard. Replicas are physical copies of each shard, acting as failover for the shard. Note that changing this value on an existing collection does not automatically add more replicas to the collection. However, it will allow add-replica commands to succeed."}}}}}],
|
||||||
|
|
|
@ -40,7 +40,6 @@ import org.apache.solr.client.solrj.response.RequestStatusState;
|
||||||
import org.apache.solr.client.solrj.util.SolrIdentifierValidator;
|
import org.apache.solr.client.solrj.util.SolrIdentifierValidator;
|
||||||
import org.apache.solr.common.MapWriter;
|
import org.apache.solr.common.MapWriter;
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
import org.apache.solr.common.cloud.DocCollection;
|
|
||||||
import org.apache.solr.common.cloud.ImplicitDocRouter;
|
import org.apache.solr.common.cloud.ImplicitDocRouter;
|
||||||
import org.apache.solr.common.cloud.Replica;
|
import org.apache.solr.common.cloud.Replica;
|
||||||
import org.apache.solr.common.cloud.ZkStateReader;
|
import org.apache.solr.common.cloud.ZkStateReader;
|
||||||
|
@ -55,8 +54,6 @@ import org.apache.solr.common.params.ShardParams;
|
||||||
import org.apache.solr.common.params.SolrParams;
|
import org.apache.solr.common.params.SolrParams;
|
||||||
import org.apache.solr.common.util.NamedList;
|
import org.apache.solr.common.util.NamedList;
|
||||||
|
|
||||||
import static org.apache.solr.common.cloud.DocCollection.RULE;
|
|
||||||
import static org.apache.solr.common.cloud.DocCollection.SNITCH;
|
|
||||||
import static org.apache.solr.common.cloud.ZkStateReader.NRT_REPLICAS;
|
import static org.apache.solr.common.cloud.ZkStateReader.NRT_REPLICAS;
|
||||||
import static org.apache.solr.common.cloud.ZkStateReader.PULL_REPLICAS;
|
import static org.apache.solr.common.cloud.ZkStateReader.PULL_REPLICAS;
|
||||||
import static org.apache.solr.common.cloud.ZkStateReader.READ_ONLY;
|
import static org.apache.solr.common.cloud.ZkStateReader.READ_ONLY;
|
||||||
|
@ -83,8 +80,6 @@ public abstract class CollectionAdminRequest<T extends CollectionAdminResponse>
|
||||||
* The set of modifiable collection properties
|
* The set of modifiable collection properties
|
||||||
*/
|
*/
|
||||||
public static final java.util.List<String> MODIFIABLE_COLLECTION_PROPERTIES = Arrays.asList(
|
public static final java.util.List<String> MODIFIABLE_COLLECTION_PROPERTIES = Arrays.asList(
|
||||||
RULE,
|
|
||||||
SNITCH,
|
|
||||||
REPLICATION_FACTOR,
|
REPLICATION_FACTOR,
|
||||||
COLL_CONF,
|
COLL_CONF,
|
||||||
WITH_COLLECTION,
|
WITH_COLLECTION,
|
||||||
|
@ -568,8 +563,6 @@ public abstract class CollectionAdminRequest<T extends CollectionAdminResponse>
|
||||||
if (tlogReplicas != null) {
|
if (tlogReplicas != null) {
|
||||||
params.set(ZkStateReader.TLOG_REPLICAS, tlogReplicas);
|
params.set(ZkStateReader.TLOG_REPLICAS, tlogReplicas);
|
||||||
}
|
}
|
||||||
if (rule != null) params.set(DocCollection.RULE, rule);
|
|
||||||
if (snitch != null) params.set(DocCollection.SNITCH, snitch);
|
|
||||||
params.setNonNull(ALIAS, alias);
|
params.setNonNull(ALIAS, alias);
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,6 @@ public class DocCollection extends ZkNodeProps implements Iterable<Slice> {
|
||||||
|
|
||||||
public static final String DOC_ROUTER = "router";
|
public static final String DOC_ROUTER = "router";
|
||||||
public static final String SHARDS = "shards";
|
public static final String SHARDS = "shards";
|
||||||
public static final String RULE = "rule";
|
|
||||||
public static final String SNITCH = "snitch";
|
|
||||||
|
|
||||||
private final int znodeVersion;
|
private final int znodeVersion;
|
||||||
|
|
||||||
|
@ -91,8 +89,6 @@ public class DocCollection extends ZkNodeProps implements Iterable<Slice> {
|
||||||
Boolean readOnly = (Boolean) verifyProp(props, READ_ONLY);
|
Boolean readOnly = (Boolean) verifyProp(props, READ_ONLY);
|
||||||
this.readOnly = readOnly == null ? Boolean.FALSE : readOnly;
|
this.readOnly = readOnly == null ? Boolean.FALSE : readOnly;
|
||||||
|
|
||||||
verifyProp(props, RULE);
|
|
||||||
verifyProp(props, SNITCH);
|
|
||||||
Iterator<Map.Entry<String, Slice>> iter = slices.entrySet().iterator();
|
Iterator<Map.Entry<String, Slice>> iter = slices.entrySet().iterator();
|
||||||
|
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
|
@ -143,8 +139,6 @@ public class DocCollection extends ZkNodeProps implements Iterable<Slice> {
|
||||||
case READ_ONLY:
|
case READ_ONLY:
|
||||||
return Boolean.parseBoolean(o.toString());
|
return Boolean.parseBoolean(o.toString());
|
||||||
case "snitch":
|
case "snitch":
|
||||||
case "rule":
|
|
||||||
return (List) o;
|
|
||||||
default:
|
default:
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,22 +76,6 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Controls whether or not the shard-replicas created for this collection will be assigned to the nodes specified by the nodeSet property in a sequential manner, or if the list of nodes should be shuffled prior to creating individual replicas. A 'false' value makes the results of a collection creation predictable and gives more exact control over the location of the individual shard-replicas, but 'true' can be a better choice for ensuring replicas are distributed evenly across nodes. This property is ignored if nodeSet is not also specified."
|
"description": "Controls whether or not the shard-replicas created for this collection will be assigned to the nodes specified by the nodeSet property in a sequential manner, or if the list of nodes should be shuffled prior to creating individual replicas. A 'false' value makes the results of a collection creation predictable and gives more exact control over the location of the individual shard-replicas, but 'true' can be a better choice for ensuring replicas are distributed evenly across nodes. This property is ignored if nodeSet is not also specified."
|
||||||
},
|
},
|
||||||
"rule": {
|
|
||||||
"type": "array",
|
|
||||||
"documentation": "https://lucene.apache.org/solr/guide/rule-based-replica-placement.html",
|
|
||||||
"description": "Defines rules for where replicas should be located in a cluster.",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"snitch": {
|
|
||||||
"type": "array",
|
|
||||||
"documentation": "https://lucene.apache.org/solr/guide/rule-based-replica-placement.html",
|
|
||||||
"description": "",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"documentation": "https://lucene.apache.org/solr/guide/defining-core-properties.html",
|
"documentation": "https://lucene.apache.org/solr/guide/defining-core-properties.html",
|
||||||
|
|
|
@ -3,22 +3,6 @@
|
||||||
"description":"Modifies specific attributes of a collection. Multiple attributes can be changed at one time.",
|
"description":"Modifies specific attributes of a collection. Multiple attributes can be changed at one time.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties":{
|
"properties":{
|
||||||
"rule": {
|
|
||||||
"type": "array",
|
|
||||||
"documentation": "https://lucene.apache.org/solr/guide/rule-based-replica-placement.html",
|
|
||||||
"description": "Modifies the rules for where replicas should be located in a cluster.",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"snitch": {
|
|
||||||
"type": "array",
|
|
||||||
"documentation": "https://lucene.apache.org/solr/guide/rule-based-replica-placement.html",
|
|
||||||
"description": "Details of the snitch provider",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"replicationFactor": {
|
"replicationFactor": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "The number of replicas to be created for each shard. Replicas are physical copies of each shard, acting as failover for the shard. Note that changing this value on an existing collection does not automatically add more replicas to the collection. However, it will allow add-replica commands to succeed."
|
"description": "The number of replicas to be created for each shard. Replicas are physical copies of each shard, acting as failover for the shard. Note that changing this value on an existing collection does not automatically add more replicas to the collection. However, it will allow add-replica commands to succeed."
|
||||||
|
|
Loading…
Reference in New Issue