You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2021/01/12 13:28:58 UTC
[lucene-solr] 01/01: SOLR-15055: Minimal version that simply vetoes
primary placements.
This is an automated email from the ASF dual-hosted git repository.
ab pushed a commit to branch jira/solr-15055-2
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git
commit c49ba2e3f4e7fd54d382ede805a30811579284dc
Author: Andrzej Bialecki <ab...@apache.org>
AuthorDate: Tue Jan 12 14:28:07 2021 +0100
SOLR-15055: Minimal version that simply vetoes primary placements.
---
.../apache/solr/cloud/ExclusiveSliceProperty.java | 6 +--
.../solr/cloud/api/collections/MigrateCmd.java | 2 +-
.../OverseerCollectionMessageHandler.java | 6 +--
.../solr/cloud/api/collections/SplitShardCmd.java | 5 ++-
.../apache/solr/cloud/overseer/ReplicaMutator.java | 11 ++---
.../apache/solr/cloud/overseer/SliceMutator.java | 4 +-
.../placement/plugins/AffinityPlacementConfig.java | 19 ++++++++
.../plugins/AffinityPlacementFactory.java | 37 ++++++++++++++--
.../solr/handler/admin/CollectionsHandler.java | 22 +++++-----
.../plugins/AffinityPlacementFactoryTest.java | 50 +++++++++++++++++++++-
.../solr/common/params/CollectionAdminParams.java | 5 +++
11 files changed, 135 insertions(+), 32 deletions(-)
diff --git a/solr/core/src/java/org/apache/solr/cloud/ExclusiveSliceProperty.java b/solr/core/src/java/org/apache/solr/cloud/ExclusiveSliceProperty.java
index 448f455..d17976e 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ExclusiveSliceProperty.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ExclusiveSliceProperty.java
@@ -29,7 +29,6 @@ import java.util.Random;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
-import org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler;
import org.apache.solr.cloud.overseer.ClusterStateMutator;
import org.apache.solr.cloud.overseer.CollectionMutator;
import org.apache.solr.cloud.overseer.SliceMutator;
@@ -40,6 +39,7 @@ import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.CollectionAdminParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -74,8 +74,8 @@ class ExclusiveSliceProperty {
ExclusiveSliceProperty(ClusterState clusterState, ZkNodeProps message) {
this.clusterState = clusterState;
String tmp = message.getStr(ZkStateReader.PROPERTY_PROP);
- if (StringUtils.startsWith(tmp, OverseerCollectionMessageHandler.COLL_PROP_PREFIX) == false) {
- tmp = OverseerCollectionMessageHandler.COLL_PROP_PREFIX + tmp;
+ if (StringUtils.startsWith(tmp, CollectionAdminParams.PROPERTY_PREFIX) == false) {
+ tmp = CollectionAdminParams.PROPERTY_PREFIX + tmp;
}
this.property = tmp.toLowerCase(Locale.ROOT);
collectionName = message.getStr(ZkStateReader.COLLECTION_PROP);
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/MigrateCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/MigrateCmd.java
index 85bac4b..2b094b2 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/MigrateCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/MigrateCmd.java
@@ -304,7 +304,7 @@ public class MigrateCmd implements OverseerCollectionMessageHandler.Cmd {
props.put(CoreAdminParams.NAME, tempCollectionReplica2);
// copy over property params:
for (String key : message.keySet()) {
- if (key.startsWith(OverseerCollectionMessageHandler.COLL_PROP_PREFIX)) {
+ if (key.startsWith(CollectionAdminParams.PROPERTY_PREFIX)) {
props.put(key, message.getStr(key));
}
}
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerCollectionMessageHandler.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerCollectionMessageHandler.java
index 4150842..64176c5 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerCollectionMessageHandler.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerCollectionMessageHandler.java
@@ -127,8 +127,6 @@ public class OverseerCollectionMessageHandler implements OverseerMessageHandler,
public static final String REQUESTID = "requestid";
- public static final String COLL_PROP_PREFIX = "property.";
-
public static final String ONLY_IF_DOWN = "onlyIfDown";
public static final String SHARD_UNIQUE = "shardUnique";
@@ -560,7 +558,7 @@ public class OverseerCollectionMessageHandler implements OverseerMessageHandler,
void addPropertyParams(ZkNodeProps message, ModifiableSolrParams params) {
// Now add the property.key=value pairs
for (String key : message.keySet()) {
- if (key.startsWith(COLL_PROP_PREFIX)) {
+ if (key.startsWith(CollectionAdminParams.PROPERTY_PREFIX)) {
params.set(key, message.getStr(key));
}
}
@@ -569,7 +567,7 @@ public class OverseerCollectionMessageHandler implements OverseerMessageHandler,
void addPropertyParams(ZkNodeProps message, Map<String, Object> map) {
// Now add the property.key=value pairs
for (String key : message.keySet()) {
- if (key.startsWith(COLL_PROP_PREFIX)) {
+ if (key.startsWith(CollectionAdminParams.PROPERTY_PREFIX)) {
map.put(key, message.getStr(key));
}
}
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java
index 770dfac..e622e84 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java
@@ -30,6 +30,7 @@ import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.*;
import org.apache.solr.common.cloud.rule.ImplicitSnitch;
+import org.apache.solr.common.params.CollectionAdminParams;
import org.apache.solr.common.params.CommonAdminParams;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.CoreAdminParams;
@@ -304,7 +305,7 @@ public class SplitShardCmd implements OverseerCollectionMessageHandler.Cmd {
propMap.put(CommonAdminParams.WAIT_FOR_FINAL_STATE, Boolean.toString(waitForFinalState));
// copy over property params:
for (String key : message.keySet()) {
- if (key.startsWith(OverseerCollectionMessageHandler.COLL_PROP_PREFIX)) {
+ if (key.startsWith(CollectionAdminParams.PROPERTY_PREFIX)) {
propMap.put(key, message.getStr(key));
}
}
@@ -472,7 +473,7 @@ public class SplitShardCmd implements OverseerCollectionMessageHandler.Cmd {
propMap.put(CoreAdminParams.NAME, solrCoreName);
// copy over property params:
for (String key : message.keySet()) {
- if (key.startsWith(OverseerCollectionMessageHandler.COLL_PROP_PREFIX)) {
+ if (key.startsWith(CollectionAdminParams.PROPERTY_PREFIX)) {
propMap.put(key, message.getStr(key));
}
}
diff --git a/solr/core/src/java/org/apache/solr/cloud/overseer/ReplicaMutator.java b/solr/core/src/java/org/apache/solr/cloud/overseer/ReplicaMutator.java
index f849143..d386880 100644
--- a/solr/core/src/java/org/apache/solr/cloud/overseer/ReplicaMutator.java
+++ b/solr/core/src/java/org/apache/solr/cloud/overseer/ReplicaMutator.java
@@ -43,6 +43,7 @@ import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.CollectionAdminParams;
import org.apache.solr.common.util.Utils;
import org.apache.solr.util.TestInjection;
import org.slf4j.Logger;
@@ -115,8 +116,8 @@ public class ReplicaMutator {
String sliceName = message.getStr(ZkStateReader.SHARD_ID_PROP);
String replicaName = message.getStr(ZkStateReader.REPLICA_PROP);
String property = message.getStr(ZkStateReader.PROPERTY_PROP).toLowerCase(Locale.ROOT);
- if (StringUtils.startsWith(property, OverseerCollectionMessageHandler.COLL_PROP_PREFIX) == false) {
- property = OverseerCollectionMessageHandler.COLL_PROP_PREFIX + property;
+ if (StringUtils.startsWith(property, CollectionAdminParams.PROPERTY_PREFIX) == false) {
+ property = CollectionAdminParams.PROPERTY_PREFIX + property;
}
property = property.toLowerCase(Locale.ROOT);
String propVal = message.getStr(ZkStateReader.PROPERTY_VALUE_PROP);
@@ -180,8 +181,8 @@ public class ReplicaMutator {
String sliceName = message.getStr(ZkStateReader.SHARD_ID_PROP);
String replicaName = message.getStr(ZkStateReader.REPLICA_PROP);
String property = message.getStr(ZkStateReader.PROPERTY_PROP).toLowerCase(Locale.ROOT);
- if (StringUtils.startsWith(property, OverseerCollectionMessageHandler.COLL_PROP_PREFIX) == false) {
- property = OverseerCollectionMessageHandler.COLL_PROP_PREFIX + property;
+ if (StringUtils.startsWith(property, CollectionAdminParams.PROPERTY_PREFIX) == false) {
+ property = CollectionAdminParams.PROPERTY_PREFIX + property;
}
DocCollection collection = clusterState.getCollection(collectionName);
@@ -309,7 +310,7 @@ public class ReplicaMutator {
replicaProps.put(ZkStateReader.REPLICA_TYPE, oldReplica.getType().toString());
// Move custom props over.
for (Map.Entry<String, Object> ent : oldReplica.getProperties().entrySet()) {
- if (ent.getKey().startsWith(OverseerCollectionMessageHandler.COLL_PROP_PREFIX)) {
+ if (ent.getKey().startsWith(CollectionAdminParams.PROPERTY_PREFIX)) {
replicaProps.put(ent.getKey(), ent.getValue());
}
}
diff --git a/solr/core/src/java/org/apache/solr/cloud/overseer/SliceMutator.java b/solr/core/src/java/org/apache/solr/cloud/overseer/SliceMutator.java
index 40ab1a3..71c88f9 100644
--- a/solr/core/src/java/org/apache/solr/cloud/overseer/SliceMutator.java
+++ b/solr/core/src/java/org/apache/solr/cloud/overseer/SliceMutator.java
@@ -27,7 +27,6 @@ import org.apache.solr.client.solrj.cloud.DistribStateManager;
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
import org.apache.solr.cloud.Overseer;
import org.apache.solr.cloud.api.collections.Assign;
-import org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Replica;
@@ -36,6 +35,7 @@ import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.ZkCoreNodeProps;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.CollectionAdminParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,7 +45,7 @@ import static org.apache.solr.common.util.Utils.makeMap;
public class SliceMutator {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- public static final String PREFERRED_LEADER_PROP = OverseerCollectionMessageHandler.COLL_PROP_PREFIX + "preferredleader";
+ public static final String PREFERRED_LEADER_PROP = CollectionAdminParams.PROPERTY_PREFIX + "preferredleader";
public static final Set<String> SLICE_UNIQUE_BOOLEAN_PROPERTIES = ImmutableSet.of(PREFERRED_LEADER_PROP);
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementConfig.java b/solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementConfig.java
index bbf8dc8..2866392 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementConfig.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementConfig.java
@@ -20,6 +20,9 @@ package org.apache.solr.cluster.placement.plugins;
import org.apache.solr.cluster.placement.PlacementPluginConfig;
import org.apache.solr.common.annotation.JsonProperty;
+import java.util.Map;
+import java.util.Objects;
+
/**
* Configuration bean for {@link AffinityPlacementFactory}.
*/
@@ -43,14 +46,30 @@ public class AffinityPlacementConfig implements PlacementPluginConfig {
@JsonProperty
public long prioritizedFreeDiskGB;
+ /**
+ * This property defines an additional constraint that primary collections (keys) should be
+ * located on the same nodes as the secondary collections (values). The plugin will assume
+ * that the secondary collection replicas are already in place and ignore candidate nodes where
+ * they are not already present.
+ */
+ @JsonProperty
+ public Map<String, String> withCollections;
+
// no-arg public constructor required for deserialization
public AffinityPlacementConfig() {
minimalFreeDiskGB = 20L;
prioritizedFreeDiskGB = 100L;
+ withCollections = Map.of();
}
public AffinityPlacementConfig(long minimalFreeDiskGB, long prioritizedFreeDiskGB) {
+ this(minimalFreeDiskGB, prioritizedFreeDiskGB, Map.of());
+ }
+
+ public AffinityPlacementConfig(long minimalFreeDiskGB, long prioritizedFreeDiskGB, Map<String, String> withCollections) {
this.minimalFreeDiskGB = minimalFreeDiskGB;
this.prioritizedFreeDiskGB = prioritizedFreeDiskGB;
+ Objects.requireNonNull(withCollections);
+ this.withCollections = withCollections;
}
}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactory.java b/solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactory.java
index 9c50289..ce79792 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactory.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactory.java
@@ -147,7 +147,7 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
@Override
public PlacementPlugin createPluginInstance() {
- return new AffinityPlacementPlugin(config.minimalFreeDiskGB, config.prioritizedFreeDiskGB);
+ return new AffinityPlacementPlugin(config.minimalFreeDiskGB, config.prioritizedFreeDiskGB, config.withCollections);
}
@Override
@@ -171,14 +171,17 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
private final long prioritizedFreeDiskGB;
+ private final Map<String, String> withCollections;
+
private final Random replicaPlacementRandom = new Random(); // ok even if random sequence is predictable.
/**
* The factory has decoded the configuration for the plugin instance and passes it the parameters it needs.
*/
- private AffinityPlacementPlugin(long minimalFreeDiskGB, long prioritizedFreeDiskGB) {
+ private AffinityPlacementPlugin(long minimalFreeDiskGB, long prioritizedFreeDiskGB, Map<String, String> withCollections) {
this.minimalFreeDiskGB = minimalFreeDiskGB;
this.prioritizedFreeDiskGB = prioritizedFreeDiskGB;
+ this.withCollections = withCollections;
// We make things reproducible in tests by using test seed if any
String seed = System.getProperty("tests.seed");
@@ -193,6 +196,8 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
Set<Node> nodes = request.getTargetNodes();
SolrCollection solrCollection = request.getCollection();
+ nodes = filterNodesWithCollection(cluster, request, nodes);
+
// Request all needed attributes
attributeFetcher.requestNodeSystemProperty(AVAILABILITY_ZONE_SYSPROP).requestNodeSystemProperty(REPLICA_TYPE_SYSPROP);
attributeFetcher
@@ -467,7 +472,7 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
if (candidateAzEntries == null) {
// This can happen because not enough nodes for the placement request or already too many nodes with replicas of
// the shard that can't accept new replicas or not enough nodes with enough free disk space.
- throw new PlacementException("Not enough nodes to place " + numReplicas + " replica(s) of type " + replicaType +
+ throw new PlacementException("Not enough eligible nodes to place " + numReplicas + " replica(s) of type " + replicaType +
" for shard " + shardName + " of collection " + solrCollection.getName());
}
@@ -529,6 +534,32 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
}
}
+ private Set<Node> filterNodesWithCollection(Cluster cluster, PlacementRequest request, Set<Node> initialNodes) throws PlacementException {
+ // if there's a `withCollection` constraint for this collection then remove nodes
+ // that are not eligible
+ String withCollectionName = withCollections.get(request.getCollection().getName());
+ if (withCollectionName == null) {
+ return initialNodes;
+ }
+ SolrCollection withCollection;
+ try {
+ withCollection = cluster.getCollection(withCollectionName);
+ } catch (Exception e) {
+ throw new PlacementException("Error getting info of withCollection=" + withCollectionName, e);
+ }
+ Set<Node> withCollectionNodes = new HashSet<>();
+ withCollection.shards().forEach(s -> s.replicas().forEach(r -> withCollectionNodes.add(r.getNode())));
+ if (withCollectionNodes.isEmpty()) {
+ throw new PlacementException("Collection " + withCollection + " defined in `withCollection` has no replicas on eligible nodes.");
+ }
+ HashSet<Node> filteredNodes = new HashSet<>(initialNodes);
+ filteredNodes.retainAll(withCollectionNodes);
+ if (filteredNodes.isEmpty()) {
+ throw new PlacementException("Collection " + withCollection + " defined in `withCollection` has no replicas on eligible nodes.");
+ }
+ return filteredNodes;
+ }
+
/**
* Comparator implementing the placement strategy based on free space and number of cores: we want to place new replicas
* on nodes with the less number of cores, but only if they do have enough disk space (expressed as a threshold value).
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index 399b87b..0dd2254 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -105,7 +105,7 @@ import static org.apache.solr.client.solrj.response.RequestStatusState.NOT_FOUND
import static org.apache.solr.client.solrj.response.RequestStatusState.RUNNING;
import static org.apache.solr.client.solrj.response.RequestStatusState.SUBMITTED;
import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
-import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler.COLL_PROP_PREFIX;
+import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_PREFIX;
import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler.CREATE_NODE_SET;
import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler.CREATE_NODE_SET_EMPTY;
import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler.CREATE_NODE_SET_SHUFFLE;
@@ -494,7 +494,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
if (shardsParam == null) h.copyFromClusterProp(props, NUM_SLICES);
for (String prop : ImmutableSet.of(NRT_REPLICAS, PULL_REPLICAS, TLOG_REPLICAS))
h.copyFromClusterProp(props, prop);
- copyPropertiesWithPrefix(req.getParams(), props, COLL_PROP_PREFIX);
+ copyPropertiesWithPrefix(req.getParams(), props, PROPERTY_PREFIX);
return copyPropertiesWithPrefix(req.getParams(), props, "router.");
}),
@@ -745,7 +745,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
SPLIT_FUZZ,
SPLIT_BY_PREFIX,
FOLLOW_ALIASES);
- return copyPropertiesWithPrefix(req.getParams(), map, COLL_PROP_PREFIX);
+ return copyPropertiesWithPrefix(req.getParams(), map, PROPERTY_PREFIX);
}),
DELETESHARD_OP(DELETESHARD, (req, rsp, h) -> {
Map<String, Object> map = copy(req.getParams().required(), null,
@@ -783,7 +783,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
CREATE_NODE_SET,
WAIT_FOR_FINAL_STATE,
FOLLOW_ALIASES);
- return copyPropertiesWithPrefix(req.getParams(), map, COLL_PROP_PREFIX);
+ return copyPropertiesWithPrefix(req.getParams(), map, PROPERTY_PREFIX);
}),
DELETEREPLICA_OP(DELETEREPLICA, (req, rsp, h) -> {
Map<String, Object> map = copy(req.getParams().required(), null,
@@ -925,7 +925,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
CREATE_NODE_SET,
FOLLOW_ALIASES,
SKIP_NODE_ASSIGNMENT);
- return copyPropertiesWithPrefix(req.getParams(), props, COLL_PROP_PREFIX);
+ return copyPropertiesWithPrefix(req.getParams(), props, PROPERTY_PREFIX);
}),
OVERSEERSTATUS_OP(OVERSEERSTATUS, (req, rsp, h) -> new LinkedHashMap<>()),
@@ -966,8 +966,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
PROPERTY_VALUE_PROP);
copy(req.getParams(), map, SHARD_UNIQUE);
String property = (String) map.get(PROPERTY_PROP);
- if (!property.startsWith(COLL_PROP_PREFIX)) {
- property = COLL_PROP_PREFIX + property;
+ if (!property.startsWith(PROPERTY_PREFIX)) {
+ property = PROPERTY_PREFIX + property;
}
boolean uniquePerSlice = Boolean.parseBoolean((String) map.get(SHARD_UNIQUE));
@@ -1000,8 +1000,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
PROPERTY_PROP);
Boolean shardUnique = Boolean.parseBoolean(req.getParams().get(SHARD_UNIQUE));
String prop = req.getParams().get(PROPERTY_PROP).toLowerCase(Locale.ROOT);
- if (!StringUtils.startsWith(prop, COLL_PROP_PREFIX)) {
- prop = COLL_PROP_PREFIX + prop;
+ if (!StringUtils.startsWith(prop, PROPERTY_PREFIX)) {
+ prop = PROPERTY_PREFIX + prop;
}
if (!shardUnique && !SliceMutator.SLICE_UNIQUE_BOOLEAN_PROPERTIES.contains(prop)) {
@@ -1019,7 +1019,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
// XXX should this command support followAliases?
MODIFYCOLLECTION_OP(MODIFYCOLLECTION, (req, rsp, h) -> {
Map<String, Object> m = copy(req.getParams(), null, CollectionAdminRequest.MODIFIABLE_COLLECTION_PROPERTIES);
- copyPropertiesWithPrefix(req.getParams(), m, COLL_PROP_PREFIX);
+ copyPropertiesWithPrefix(req.getParams(), m, PROPERTY_PREFIX);
if (m.isEmpty()) {
throw new SolrException(ErrorCode.BAD_REQUEST,
formatString("no supported values provided {0}", CollectionAdminRequest.MODIFIABLE_COLLECTION_PROPERTIES.toString()));
@@ -1147,7 +1147,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
// from CREATE_OP:
copy(req.getParams(), params, COLL_CONF, REPLICATION_FACTOR, NRT_REPLICAS, TLOG_REPLICAS,
PULL_REPLICAS, CREATE_NODE_SET, CREATE_NODE_SET_SHUFFLE);
- copyPropertiesWithPrefix(req.getParams(), params, COLL_PROP_PREFIX);
+ copyPropertiesWithPrefix(req.getParams(), params, PROPERTY_PREFIX);
return params;
}),
CREATESNAPSHOT_OP(CREATESNAPSHOT, (req, rsp, h) -> {
diff --git a/solr/core/src/test/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactoryTest.java b/solr/core/src/test/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactoryTest.java
index 81dda9d..80c9b1d 100644
--- a/solr/core/src/test/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactoryTest.java
+++ b/solr/core/src/test/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactoryTest.java
@@ -50,10 +50,15 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
private final static long MINIMAL_FREE_DISK_GB = 10L;
private final static long PRIORITIZED_FREE_DISK_GB = 50L;
+ private final static String secondaryCollectionName = "withCollection_secondary";
+ private final static String primaryCollectionName = "withCollection_primary";
@BeforeClass
public static void setupPlugin() {
- AffinityPlacementConfig config = new AffinityPlacementConfig(MINIMAL_FREE_DISK_GB, PRIORITIZED_FREE_DISK_GB);
+ AffinityPlacementConfig config = new AffinityPlacementConfig(
+ MINIMAL_FREE_DISK_GB,
+ PRIORITIZED_FREE_DISK_GB,
+ Map.of(primaryCollectionName, secondaryCollectionName));
AffinityPlacementFactory factory = new AffinityPlacementFactory();
factory.configure(config);
plugin = factory.createPluginInstance();
@@ -650,6 +655,49 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
}
}
+ @Test
+ public void testWithCollectionConstraints() throws Exception {
+ int NUM_NODES = 3;
+ Builders.ClusterBuilder clusterBuilder = Builders.newClusterBuilder().initializeLiveNodes(NUM_NODES);
+ Builders.CollectionBuilder collectionBuilder = Builders.newCollectionBuilder(secondaryCollectionName);
+ collectionBuilder.initializeShardsReplicas(1, 2, 0, 0, clusterBuilder.getLiveNodeBuilders());
+ clusterBuilder.addCollection(collectionBuilder);
+
+ collectionBuilder = Builders.newCollectionBuilder(primaryCollectionName);
+ collectionBuilder.initializeShardsReplicas(0, 0, 0, 0, clusterBuilder.getLiveNodeBuilders());
+ clusterBuilder.addCollection(collectionBuilder);
+
+ Cluster cluster = clusterBuilder.build();
+
+ SolrCollection secondaryCollection = cluster.getCollection(secondaryCollectionName);
+ SolrCollection primaryCollection = cluster.getCollection(primaryCollectionName);
+
+ Set<Node> secondaryNodes = new HashSet<>();
+ secondaryCollection.shards().forEach(s -> s.replicas().forEach(r -> secondaryNodes.add(r.getNode())));
+
+ PlacementRequestImpl placementRequest = new PlacementRequestImpl(primaryCollection,
+ Set.of("shard1", "shard2"), cluster.getLiveNodes(), 1, 0, 0);
+
+
+ PlacementPlanFactory placementPlanFactory = new PlacementPlanFactoryImpl();
+ AttributeFetcher attributeFetcher = clusterBuilder.buildAttributeFetcher();
+ PlacementPlan pp = plugin.computePlacement(cluster, placementRequest, attributeFetcher, placementPlanFactory);
+ assertEquals(2, pp.getReplicaPlacements().size());
+ // verify that all placements are on nodes with the secondary replica
+ pp.getReplicaPlacements().forEach(placement ->
+ assertTrue("placement node " + placement.getNode() + " not in secondary=" + secondaryNodes,
+ secondaryNodes.contains(placement.getNode())));
+
+ placementRequest = new PlacementRequestImpl(primaryCollection,
+ Set.of("shard1"), cluster.getLiveNodes(), 3, 0, 0);
+ try {
+ pp = plugin.computePlacement(cluster, placementRequest, attributeFetcher, placementPlanFactory);
+ fail("should generate 'Not enough eligible nodes' failure here");
+ } catch (PlacementException pe) {
+ assertTrue(pe.toString().contains("Not enough eligible nodes"));
+ }
+ }
+
@Test @Slow
public void testScalability() throws Exception {
log.info("==== numNodes ====");
diff --git a/solr/solrj/src/java/org/apache/solr/common/params/CollectionAdminParams.java b/solr/solrj/src/java/org/apache/solr/common/params/CollectionAdminParams.java
index fee3b7a..c38f397 100644
--- a/solr/solrj/src/java/org/apache/solr/common/params/CollectionAdminParams.java
+++ b/solr/solrj/src/java/org/apache/solr/common/params/CollectionAdminParams.java
@@ -115,4 +115,9 @@ public interface CollectionAdminParams {
* for the add replica API. If set to true, a valid "node" should be specified.
*/
String SKIP_NODE_ASSIGNMENT = "skipNodeAssignment";
+
+ /**
+ * Prefix for arbitrary collection or replica properties.
+ */
+ String PROPERTY_PREFIX = "property.";
}