You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ds...@apache.org on 2023/11/21 18:55:00 UTC
(solr) branch branch_9x updated: SOLR-17079: Allow to declare replica placement plugins in solr.xml (#2071)
This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/branch_9x by this push:
new 9ed035cdd5f SOLR-17079: Allow to declare replica placement plugins in solr.xml (#2071)
9ed035cdd5f is described below
commit 9ed035cdd5f7dc638e4e0ffee7b2c79ef81a4a11
Author: Vincent P <vi...@gmail.com>
AuthorDate: Mon Nov 20 05:44:41 2023 +0100
SOLR-17079: Allow to declare replica placement plugins in solr.xml (#2071)
Co-authored-by: Vincent Primault <vp...@salesforce.com>
---
solr/CHANGES.txt | 2 +
.../apache/solr/api/ContainerPluginsRegistry.java | 31 +++++---
.../apache/solr/cloud/api/collections/Assign.java | 37 ---------
.../cluster/placement/PlacementPluginFactory.java | 4 -
.../impl/DelegatingPlacementPluginFactory.java | 24 +++++-
.../impl/PlacementPluginFactoryLoader.java | 87 +++++++++++++++++++++-
.../plugins/AffinityPlacementFactory.java | 2 +-
.../java/org/apache/solr/core/CoreContainer.java | 7 +-
.../src/java/org/apache/solr/core/NodeConfig.java | 16 +++-
.../java/org/apache/solr/core/SolrXmlConfig.java | 2 +
solr/core/src/test-files/solr/solr-50-all.xml | 5 ++
.../OverseerCollectionConfigSetProcessorTest.java | 9 +--
.../impl/PlacementPluginIntegrationTest.java | 50 +++++++++++++
.../src/test/org/apache/solr/core/TestSolrXml.java | 8 ++
.../pages/configuring-solr-xml.adoc | 15 ++++
.../pages/replica-placement-plugins.adoc | 3 +-
16 files changed, 234 insertions(+), 68 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 578bccc4589..968f75e2475 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -13,6 +13,8 @@ New Features
* SOLR-17006: Collection creation & adding replicas: User-defined properties are persisted to state.json and
applied to new replicas, available for use as property substitution in configuration files. (Vincent Primault)
+* SOLR-17079: Allow to declare replica placement plugins in solr.xml (Vincent Primault)
+
Improvements
---------------------
* SOLR-16924: RESTORECORE now sets the UpdateLog to ACTIVE state instead of requiring a separate
diff --git a/solr/core/src/java/org/apache/solr/api/ContainerPluginsRegistry.java b/solr/core/src/java/org/apache/solr/api/ContainerPluginsRegistry.java
index 3fea96c2ba8..fbfa6719e8e 100644
--- a/solr/core/src/java/org/apache/solr/api/ContainerPluginsRegistry.java
+++ b/solr/core/src/java/org/apache/solr/api/ContainerPluginsRegistry.java
@@ -420,16 +420,9 @@ public class ContainerPluginsRegistry implements ClusterPropertiesListener, MapW
} else {
throw new RuntimeException("Must have a no-arg constructor or CoreContainer constructor ");
}
- if (instance instanceof ConfigurablePlugin) {
- Class<? extends MapWriter> c =
- getConfigClass((ConfigurablePlugin<? extends MapWriter>) instance);
- if (c != null) {
- Map<String, Object> original =
- (Map<String, Object>) holder.original.getOrDefault("config", Collections.emptyMap());
- holder.meta.config = mapper.readValue(Utils.toJSON(original), c);
- ((ConfigurablePlugin<MapWriter>) instance).configure(holder.meta.config);
- }
- }
+ Map<String, Object> config =
+ (Map<String, Object>) holder.original.getOrDefault("config", Collections.emptyMap());
+ configure(instance, config, holder.meta);
if (instance instanceof ResourceLoaderAware) {
try {
((ResourceLoaderAware) instance).inform(pkgVersion.getLoader());
@@ -444,6 +437,24 @@ public class ContainerPluginsRegistry implements ClusterPropertiesListener, MapW
}
}
+ @SuppressWarnings("unchecked")
+ public static MapWriter configure(Object instance, Map<String, Object> config, PluginMeta meta)
+ throws IOException {
+ if (instance instanceof ConfigurablePlugin) {
+ Class<? extends MapWriter> c =
+ getConfigClass((ConfigurablePlugin<? extends MapWriter>) instance);
+ if (c != null) {
+ MapWriter configObj = mapper.readValue(Utils.toJSON(config), c);
+ if (null != meta) {
+ meta.config = configObj;
+ }
+ ((ConfigurablePlugin<MapWriter>) instance).configure(configObj);
+ return configObj;
+ }
+ }
+ return null;
+ }
+
/** Get the generic type of a {@link ConfigurablePlugin} */
@SuppressWarnings("unchecked")
public static <T extends MapWriter> Class<T> getConfigClass(ConfigurablePlugin<T> o) {
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
index 6304e522209..8652157e640 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
@@ -41,9 +41,6 @@ import org.apache.solr.client.solrj.cloud.SolrCloudManager;
import org.apache.solr.client.solrj.cloud.VersionedData;
import org.apache.solr.cluster.placement.PlacementPlugin;
import org.apache.solr.cluster.placement.impl.PlacementPluginAssignStrategy;
-import org.apache.solr.cluster.placement.plugins.AffinityPlacementFactory;
-import org.apache.solr.cluster.placement.plugins.MinimizeCoresPlacementFactory;
-import org.apache.solr.cluster.placement.plugins.RandomPlacementFactory;
import org.apache.solr.cluster.placement.plugins.SimplePlacementFactory;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ClusterState;
@@ -579,40 +576,6 @@ public class Assign {
// placement plugin)
PlacementPlugin placementPlugin =
coreContainer.getPlacementPluginFactory().createPluginInstance();
- if (placementPlugin == null) {
- // Otherwise use the default
- String defaultPluginId = System.getProperty(PLACEMENTPLUGIN_DEFAULT_SYSPROP);
- if (defaultPluginId != null) {
- switch (defaultPluginId.toLowerCase(Locale.ROOT)) {
- case "simple":
- placementPlugin = (new SimplePlacementFactory()).createPluginInstance();
- break;
- case "affinity":
- placementPlugin = (new AffinityPlacementFactory()).createPluginInstance();
- break;
- case "minimizecores":
- placementPlugin = (new MinimizeCoresPlacementFactory()).createPluginInstance();
- break;
- case "random":
- placementPlugin = (new RandomPlacementFactory()).createPluginInstance();
- break;
- default:
- throw new SolrException(
- SolrException.ErrorCode.SERVER_ERROR,
- "Invalid value for system property '"
- + PLACEMENTPLUGIN_DEFAULT_SYSPROP
- + "'. Supported values are 'simple', 'random', 'affinity' and 'minimizecores'");
- }
- log.info(
- "Default replica placement plugin set in {} to {}",
- PLACEMENTPLUGIN_DEFAULT_SYSPROP,
- defaultPluginId);
- } else {
- // TODO: Consider making the ootb default AffinityPlacementFactory, see
- // https://issues.apache.org/jira/browse/SOLR-16492
- placementPlugin = (new SimplePlacementFactory()).createPluginInstance();
- }
- }
return new PlacementPluginAssignStrategy(placementPlugin);
}
}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/PlacementPluginFactory.java b/solr/core/src/java/org/apache/solr/cluster/placement/PlacementPluginFactory.java
index 212c92906dd..da9fac5c3da 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/PlacementPluginFactory.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/PlacementPluginFactory.java
@@ -18,7 +18,6 @@
package org.apache.solr.cluster.placement;
import org.apache.solr.api.ConfigurablePlugin;
-import org.apache.solr.cluster.placement.plugins.SimplePlacementFactory;
/**
* Factory implemented by client code and configured in container plugins (see {@link
@@ -37,9 +36,6 @@ public interface PlacementPluginFactory<T extends PlacementPluginConfig>
* Returns an instance of the plugin that will be repeatedly (and concurrently) called to compute
* placement. Multiple instances of a plugin can be used in parallel (for example if configuration
* has to change, but plugin instances with the previous configuration are still being used).
- *
- * <p>If this method returns null then a simple default assignment strategy will be used (see
- * {@link SimplePlacementFactory}).
*/
PlacementPlugin createPluginInstance();
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/DelegatingPlacementPluginFactory.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/DelegatingPlacementPluginFactory.java
index 2817ded862c..0fbc6ae27a4 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/DelegatingPlacementPluginFactory.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/DelegatingPlacementPluginFactory.java
@@ -24,17 +24,37 @@ import org.apache.solr.cluster.placement.PlacementPluginFactory;
/** Helper class to support dynamic reloading of plugin implementations. */
public final class DelegatingPlacementPluginFactory
- implements PlacementPluginFactory<PlacementPluginFactory.NoConfig> {
+ implements PlacementPluginFactory<PlacementPluginConfig> {
private volatile PlacementPluginFactory<? extends PlacementPluginConfig> delegate;
// support for tests to make sure the update is completed
private volatile Phaser phaser;
+ private final PlacementPluginFactory<?> defaultPlacementPluginFactory;
+
+ /**
+ * Constructor.
+ *
+ * @param defaultPlacementPluginFactory A {@link PlacementPluginFactory} to use when no delegate
+ * is defined.
+ */
+ public DelegatingPlacementPluginFactory(PlacementPluginFactory<?> defaultPlacementPluginFactory) {
+ this.defaultPlacementPluginFactory = defaultPlacementPluginFactory;
+ }
@Override
public PlacementPlugin createPluginInstance() {
if (delegate != null) {
return delegate.createPluginInstance();
} else {
- return null;
+ return defaultPlacementPluginFactory.createPluginInstance();
+ }
+ }
+
+ @Override
+ public PlacementPluginConfig getConfig() {
+ if (delegate != null) {
+ return delegate.getConfig();
+ } else {
+ return defaultPlacementPluginFactory.getConfig();
}
}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/PlacementPluginFactoryLoader.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/PlacementPluginFactoryLoader.java
index 0728c8c221c..45d10012028 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/PlacementPluginFactoryLoader.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/PlacementPluginFactoryLoader.java
@@ -17,21 +17,38 @@
package org.apache.solr.cluster.placement.impl;
+import com.google.common.annotations.VisibleForTesting;
+import java.io.IOException;
import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
import org.apache.solr.api.ContainerPluginsRegistry;
import org.apache.solr.client.solrj.request.beans.PluginMeta;
import org.apache.solr.cluster.placement.PlacementPluginConfig;
import org.apache.solr.cluster.placement.PlacementPluginFactory;
+import org.apache.solr.cluster.placement.plugins.AffinityPlacementFactory;
+import org.apache.solr.cluster.placement.plugins.MinimizeCoresPlacementFactory;
+import org.apache.solr.cluster.placement.plugins.RandomPlacementFactory;
+import org.apache.solr.cluster.placement.plugins.SimplePlacementFactory;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.core.NodeConfig;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrResourceLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-/**
- * Utility class to load the configured {@link PlacementPluginFactory} plugin and then keep it up to
- * date as the plugin configuration changes.
- */
+/** Utility class to work with {@link PlacementPluginFactory} plugins. */
public class PlacementPluginFactoryLoader {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ @VisibleForTesting
+ static final String PLACEMENTPLUGIN_DEFAULT_SYSPROP = "solr.placementplugin.default";
+
+ /**
+ * Loads the {@link PlacementPluginFactory} configured in cluster plugins and then keep it up to
+ * date as the plugin configuration changes.
+ */
public static void load(
DelegatingPlacementPluginFactory pluginFactory, ContainerPluginsRegistry plugins) {
ContainerPluginsRegistry.ApiInfo pluginFactoryInfo =
@@ -87,4 +104,66 @@ public class PlacementPluginFactoryLoader {
};
plugins.registerListener(pluginListener);
}
+
+ /** Returns the default {@link PlacementPluginFactory} configured in solr.xml. */
+ public static PlacementPluginFactory<?> getDefaultPlacementPluginFactory(
+ NodeConfig nodeConfig, SolrResourceLoader loader) {
+ PluginInfo pluginInfo = nodeConfig.getReplicaPlacementFactoryConfig();
+ if (null != pluginInfo) {
+ return getPlacementPluginFactory(pluginInfo, loader);
+ } else {
+ return getDefaultPlacementPluginFactory();
+ }
+ }
+
+ private static PlacementPluginFactory<?> getPlacementPluginFactory(
+ PluginInfo pluginInfo, SolrResourceLoader loader) {
+ // Load placement plugin factory from solr.xml.
+ PlacementPluginFactory<?> placementPluginFactory =
+ loader.newInstance(pluginInfo, PlacementPluginFactory.class, false);
+ if (null != pluginInfo.initArgs) {
+ Map<String, Object> config = new HashMap<>();
+ pluginInfo.initArgs.toMap(config);
+ try {
+ ContainerPluginsRegistry.configure(placementPluginFactory, config, null);
+ } catch (IOException e) {
+ throw new SolrException(
+ SolrException.ErrorCode.SERVER_ERROR,
+ "Invalid " + pluginInfo.type + " configuration",
+ e);
+ }
+ }
+ return placementPluginFactory;
+ }
+
+ private static PlacementPluginFactory<?> getDefaultPlacementPluginFactory() {
+ // Otherwise use the default provided by system properties.
+ String defaultPluginId = System.getProperty(PLACEMENTPLUGIN_DEFAULT_SYSPROP);
+ if (defaultPluginId != null) {
+ log.info(
+ "Default replica placement plugin set in {} to {}",
+ PLACEMENTPLUGIN_DEFAULT_SYSPROP,
+ defaultPluginId);
+ switch (defaultPluginId.toLowerCase(Locale.ROOT)) {
+ case "simple":
+ return new SimplePlacementFactory();
+ case "affinity":
+ return new AffinityPlacementFactory();
+ case "minimizecores":
+ return new MinimizeCoresPlacementFactory();
+ case "random":
+ return new RandomPlacementFactory();
+ default:
+ throw new SolrException(
+ SolrException.ErrorCode.SERVER_ERROR,
+ "Invalid value for system property '"
+ + PLACEMENTPLUGIN_DEFAULT_SYSPROP
+ + "'. Supported values are 'simple', 'random', 'affinity' and 'minimizecores'");
+ }
+ } else {
+ // TODO: Consider making the ootb default AffinityPlacementFactory, see
+ // https://issues.apache.org/jira/browse/SOLR-16492
+ return new SimplePlacementFactory();
+ }
+ }
}
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 b397cab6722..6c339aa7094 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
@@ -150,7 +150,7 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
* See {@link AffinityPlacementFactory} for instructions on how to configure a cluster to use this
* plugin and details on what the plugin does.
*/
- static class AffinityPlacementPlugin extends OrderedNodePlacementPlugin {
+ public static class AffinityPlacementPlugin extends OrderedNodePlacementPlugin {
private final long minimalFreeDiskGB;
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index b2157504a71..a4d3518fe5f 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -292,8 +292,7 @@ public class CoreContainer {
(r) -> this.runAsync(r));
private volatile ClusterEventProducer clusterEventProducer;
- private final DelegatingPlacementPluginFactory placementPluginFactory =
- new DelegatingPlacementPluginFactory();
+ private DelegatingPlacementPluginFactory placementPluginFactory;
private FileStoreAPI fileStoreAPI;
private SolrPackageLoader packageLoader;
@@ -778,6 +777,10 @@ public class CoreContainer {
ClusterEventProducerFactory clusterEventProducerFactory = new ClusterEventProducerFactory(this);
clusterEventProducer = clusterEventProducerFactory;
+ placementPluginFactory =
+ new DelegatingPlacementPluginFactory(
+ PlacementPluginFactoryLoader.getDefaultPlacementPluginFactory(cfg, loader));
+
containerPluginsRegistry.registerListener(clusterSingletons.getPluginRegistryListener());
containerPluginsRegistry.registerListener(
clusterEventProducerFactory.getPluginRegistryListener());
diff --git a/solr/core/src/java/org/apache/solr/core/NodeConfig.java b/solr/core/src/java/org/apache/solr/core/NodeConfig.java
index 75e570b2235..2e9503022f2 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeConfig.java
@@ -78,8 +78,8 @@ public class NodeConfig {
private final Predicate<String> hiddenSysPropPattern;
private final PluginInfo shardHandlerFactoryConfig;
-
private final UpdateShardHandlerConfig updateShardHandlerConfig;
+ private final PluginInfo replicaPlacementFactoryConfig;
private final String configSetServiceClass;
@@ -133,6 +133,7 @@ public class NodeConfig {
String sharedLibDirectory,
PluginInfo shardHandlerFactoryConfig,
UpdateShardHandlerConfig updateShardHandlerConfig,
+ PluginInfo replicaPlacementFactoryConfig,
String coreAdminHandlerClass,
Map<String, String> coreAdminHandlerActions,
String collectionsAdminHandlerClass,
@@ -171,6 +172,7 @@ public class NodeConfig {
this.sharedLibDirectory = sharedLibDirectory;
this.shardHandlerFactoryConfig = shardHandlerFactoryConfig;
this.updateShardHandlerConfig = updateShardHandlerConfig;
+ this.replicaPlacementFactoryConfig = replicaPlacementFactoryConfig;
this.coreAdminHandlerClass = coreAdminHandlerClass;
this.coreAdminHandlerActions = coreAdminHandlerActions;
this.collectionsAdminHandlerClass = collectionsAdminHandlerClass;
@@ -314,6 +316,10 @@ public class NodeConfig {
return updateShardHandlerConfig;
}
+ public PluginInfo getReplicaPlacementFactoryConfig() {
+ return replicaPlacementFactoryConfig;
+ }
+
public int getCoreLoadThreadCount(boolean zkAware) {
return coreLoadThreads == null
? (zkAware
@@ -608,6 +614,7 @@ public class NodeConfig {
private String hiddenSysProps;
private PluginInfo shardHandlerFactoryConfig;
private UpdateShardHandlerConfig updateShardHandlerConfig = UpdateShardHandlerConfig.DEFAULT;
+ private PluginInfo replicaPlacementFactoryConfig;
private String configSetServiceClass;
private String coreAdminHandlerClass = DEFAULT_ADMINHANDLERCLASS;
private Map<String, String> coreAdminHandlerActions = Collections.emptyMap();
@@ -720,6 +727,12 @@ public class NodeConfig {
return this;
}
+ public NodeConfigBuilder setReplicaPlacementFactoryConfig(
+ PluginInfo replicaPlacementFactoryConfig) {
+ this.replicaPlacementFactoryConfig = replicaPlacementFactoryConfig;
+ return this;
+ }
+
public NodeConfigBuilder setCoreAdminHandlerClass(String coreAdminHandlerClass) {
this.coreAdminHandlerClass = coreAdminHandlerClass;
return this;
@@ -909,6 +922,7 @@ public class NodeConfig {
sharedLibDirectory,
shardHandlerFactoryConfig,
updateShardHandlerConfig,
+ replicaPlacementFactoryConfig,
coreAdminHandlerClass,
coreAdminHandlerActions,
collectionsAdminHandlerClass,
diff --git a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
index 78228f44507..3a75f9683b1 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
@@ -167,6 +167,8 @@ public class SolrXmlConfig {
configBuilder.setSolrResourceLoader(loader);
configBuilder.setUpdateShardHandlerConfig(updateConfig);
configBuilder.setShardHandlerFactoryConfig(getPluginInfo(root.get("shardHandlerFactory")));
+ configBuilder.setReplicaPlacementFactoryConfig(
+ getPluginInfo(root.get("replicaPlacementFactory")));
configBuilder.setTracerConfig(getPluginInfo(root.get("tracerConfig")));
configBuilder.setLogWatcherConfig(loadLogWatcherConfig(root.get("logging")));
configBuilder.setSolrProperties(loadProperties(root, substituteProperties));
diff --git a/solr/core/src/test-files/solr/solr-50-all.xml b/solr/core/src/test-files/solr/solr-50-all.xml
index 0858a68b2cd..e1c34657c66 100644
--- a/solr/core/src/test-files/solr/solr-50-all.xml
+++ b/solr/core/src/test-files/solr/solr-50-all.xml
@@ -26,6 +26,7 @@
<str name="sharedLib">testSharedLib</str>
<str name="allowPaths">${solr.allowPaths:}</str>
<str name="shareSchema">${shareSchema:true}</str>
+ <str name="coresLocator">testCoresLocator</str>
<int name="transientCacheSize">66</int>
<int name="replayUpdatesThreads">100</int>
<int name="maxBooleanClauses">42</int>
@@ -67,6 +68,10 @@
<int name="connTimeout">${connTimeout:110}</int>
</shardHandlerFactory>
+ <replicaPlacementFactory class="org.apache.solr.cluster.placement.plugins.AffinityPlacementFactory">
+ <int name="minimalFreeDiskGB">10</int>
+ </replicaPlacementFactory>
+
<backup>
<repository name="local" class="a.b.C" default="true"/>
</backup>
diff --git a/solr/core/src/test/org/apache/solr/cloud/OverseerCollectionConfigSetProcessorTest.java b/solr/core/src/test/org/apache/solr/cloud/OverseerCollectionConfigSetProcessorTest.java
index ac3df8b4373..5e179e0d0f5 100644
--- a/solr/core/src/test/org/apache/solr/cloud/OverseerCollectionConfigSetProcessorTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/OverseerCollectionConfigSetProcessorTest.java
@@ -56,6 +56,7 @@ import org.apache.solr.cloud.Overseer.LeaderStatus;
import org.apache.solr.cloud.OverseerTaskQueue.QueueEvent;
import org.apache.solr.cloud.api.collections.CollectionHandlingUtils;
import org.apache.solr.cluster.placement.PlacementPluginFactory;
+import org.apache.solr.cluster.placement.plugins.SimplePlacementFactory;
import org.apache.solr.common.cloud.Aliases;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
@@ -129,7 +130,7 @@ public class OverseerCollectionConfigSetProcessorTest extends SolrTestCaseJ4 {
private static HttpClient httpClientMock;
@SuppressWarnings("rawtypes")
- private static PlacementPluginFactory placementPluginFactoryMock;
+ private final PlacementPluginFactory placementPluginFactory = new SimplePlacementFactory();
private static SolrMetricsContext solrMetricsContextMock;
@@ -206,7 +207,6 @@ public class OverseerCollectionConfigSetProcessorTest extends SolrTestCaseJ4 {
coreContainerMock = mock(CoreContainer.class);
updateShardHandlerMock = mock(UpdateShardHandler.class);
httpClientMock = mock(HttpClient.class);
- placementPluginFactoryMock = mock(PlacementPluginFactory.class);
solrMetricsContextMock = mock(SolrMetricsContext.class);
}
@@ -229,13 +229,11 @@ public class OverseerCollectionConfigSetProcessorTest extends SolrTestCaseJ4 {
cloudDataProviderMock = null;
clusterStateProviderMock = null;
stateManagerMock = null;
- ;
cloudManagerMock = null;
distribStateManagerMock = null;
coreContainerMock = null;
updateShardHandlerMock = null;
httpClientMock = null;
- placementPluginFactoryMock = null;
solrMetricsContextMock = null;
}
@@ -269,7 +267,6 @@ public class OverseerCollectionConfigSetProcessorTest extends SolrTestCaseJ4 {
reset(coreContainerMock);
reset(updateShardHandlerMock);
reset(httpClientMock);
- reset(placementPluginFactoryMock);
reset(solrMetricsContextMock);
zkClientData.clear();
@@ -446,7 +443,7 @@ public class OverseerCollectionConfigSetProcessorTest extends SolrTestCaseJ4 {
when(distributedClusterStateUpdater.createStateChangeRecorder(any(), anyBoolean()))
.thenReturn(stateChangeRecorder);
when(coreContainerMock.getUpdateShardHandler()).thenReturn(updateShardHandlerMock);
- when(coreContainerMock.getPlacementPluginFactory()).thenReturn(placementPluginFactoryMock);
+ when(coreContainerMock.getPlacementPluginFactory()).thenReturn(placementPluginFactory);
when(coreContainerMock.getConfigSetService())
.thenReturn(new ZkConfigSetService(solrZkClientMock));
when(updateShardHandlerMock.getDefaultHttpClient()).thenReturn(httpClientMock);
diff --git a/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java b/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java
index ecbf3fb5de4..fadc23c6b27 100644
--- a/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java
@@ -18,6 +18,7 @@
package org.apache.solr.cluster.placement.impl;
import static java.util.Collections.singletonMap;
+import static org.hamcrest.Matchers.instanceOf;
import java.util.Arrays;
import java.util.HashMap;
@@ -29,6 +30,7 @@ import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.lucene.tests.util.TestRuleRestoreSystemProperties;
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.V2Request;
@@ -51,13 +53,18 @@ import org.apache.solr.cluster.placement.ShardMetrics;
import org.apache.solr.cluster.placement.plugins.AffinityPlacementConfig;
import org.apache.solr.cluster.placement.plugins.AffinityPlacementFactory;
import org.apache.solr.cluster.placement.plugins.MinimizeCoresPlacementFactory;
+import org.apache.solr.cluster.placement.plugins.RandomPlacementFactory;
+import org.apache.solr.cluster.placement.plugins.SimplePlacementFactory;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.util.LogLevel;
+import org.hamcrest.MatcherAssert;
import org.junit.After;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
/** Test for {@link MinimizeCoresPlacementFactory} using a {@link MiniSolrCloudCluster}. */
@LogLevel("org.apache.solr.cluster.placement.impl=DEBUG")
@@ -65,6 +72,11 @@ public class PlacementPluginIntegrationTest extends SolrCloudTestCase {
private static final String COLLECTION =
PlacementPluginIntegrationTest.class.getSimpleName() + "_collection";
+ @Rule
+ public TestRule sysPropRestore =
+ new TestRuleRestoreSystemProperties(
+ PlacementPluginFactoryLoader.PLACEMENTPLUGIN_DEFAULT_SYSPROP);
+
private static SolrCloudManager cloudManager;
private static CoreContainer cc;
@@ -93,6 +105,44 @@ public class PlacementPluginIntegrationTest extends SolrCloudTestCase {
}
}
+ @Test
+ public void testDefaultConfiguration() {
+ CoreContainer cc = createCoreContainer(TEST_PATH(), "<solr></solr>");
+ MatcherAssert.assertThat(
+ cc.getPlacementPluginFactory().createPluginInstance(),
+ instanceOf(SimplePlacementFactory.SimplePlacementPlugin.class));
+ cc.shutdown();
+ }
+
+ @Test
+ public void testConfigurationInSystemProps() {
+ System.setProperty(PlacementPluginFactoryLoader.PLACEMENTPLUGIN_DEFAULT_SYSPROP, "random");
+ CoreContainer cc = createCoreContainer(TEST_PATH(), "<solr></solr>");
+ MatcherAssert.assertThat(
+ cc.getPlacementPluginFactory().createPluginInstance(),
+ instanceOf(RandomPlacementFactory.RandomPlacementPlugin.class));
+ cc.shutdown();
+ }
+
+ @Test
+ public void testConfigurationInSolrXml() {
+ String solrXml =
+ "<solr><replicaPlacementFactory class=\"org.apache.solr.cluster.placement.plugins.AffinityPlacementFactory\"><int name=\"minimalFreeDiskGB\">10</int><int name=\"prioritizedFreeDiskGB\">200</int></replicaPlacementFactory></solr>";
+ CoreContainer cc = createCoreContainer(TEST_PATH(), solrXml);
+
+ MatcherAssert.assertThat(
+ cc.getPlacementPluginFactory().createPluginInstance(),
+ instanceOf(AffinityPlacementFactory.AffinityPlacementPlugin.class));
+ MatcherAssert.assertThat(
+ cc.getPlacementPluginFactory().getConfig(), instanceOf(AffinityPlacementConfig.class));
+
+ AffinityPlacementConfig config =
+ (AffinityPlacementConfig) cc.getPlacementPluginFactory().getConfig();
+ assertEquals(config.minimalFreeDiskGB, 10);
+ assertEquals(config.prioritizedFreeDiskGB, 200);
+ cc.shutdown();
+ }
+
@Test
public void testMinimizeCores() throws Exception {
PluginMeta plugin = new PluginMeta();
diff --git a/solr/core/src/test/org/apache/solr/core/TestSolrXml.java b/solr/core/src/test/org/apache/solr/core/TestSolrXml.java
index 03d7fb61abf..ea67bc5033d 100644
--- a/solr/core/src/test/org/apache/solr/core/TestSolrXml.java
+++ b/solr/core/src/test/org/apache/solr/core/TestSolrXml.java
@@ -78,6 +78,7 @@ public class TestSolrXml extends SolrTestCaseJ4 {
assertEquals("info handler class", "testInfoHandler", cfg.getInfoHandlerClass());
assertEquals(
"config set handler class", "testConfigSetsHandler", cfg.getConfigSetsHandlerClass());
+ assertEquals("cores locator class", "testCoresLocator", cfg.getCoresLocatorClass());
assertEquals("core load threads", 11, cfg.getCoreLoadThreadCount(false));
assertEquals("replay update threads", 100, cfg.getReplayUpdatesThreads());
MatcherAssert.assertThat(
@@ -138,6 +139,13 @@ public class TestSolrXml extends SolrTestCaseJ4 {
.collect(Collectors.toSet())));
assertTrue("hideStackTrace", cfg.hideStackTraces());
System.clearProperty("solr.allowPaths");
+
+ PluginInfo replicaPlacementFactoryConfig = cfg.getReplicaPlacementFactoryConfig();
+ assertEquals(
+ "org.apache.solr.cluster.placement.plugins.AffinityPlacementFactory",
+ replicaPlacementFactoryConfig.className);
+ assertEquals(1, replicaPlacementFactoryConfig.initArgs.size());
+ assertEquals(10, replicaPlacementFactoryConfig.initArgs.get("minimalFreeDiskGB"));
}
// Test a few property substitutions that happen to be in solr-50-all.xml.
diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc
index 9b33d22512c..1abb7dce387 100644
--- a/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc
+++ b/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc
@@ -653,6 +653,21 @@ For configuring `stable` routing, the `hash` parameter implicitly defaults to a
The `dividend` parameter must be configured explicitly; there is no implicit default.
If only `dividend` routing is desired, `hash` may be explicitly set to the empty string, entirely disabling implicit hash-based routing.
+=== The <replicaPlacementFactory> Element
+
+A default xref:replica-placement-plugins.adoc[replica placement plugin] can be defined in `solr.xml`.
+
+[source,xml]
+----
+<replicaPlacementFactory class="org.apache.solr.cluster.placement.plugins.AffinityPlacementFactory">
+ <int name="minimalFreeDiskGB">10</int>
+ <int name="prioritizedFreeDiskGB">200</int>
+</replicaPlacementFactory>
+----
+
+The `class` attribute should be set to the FQN (fully qualified name) of a class that extends `PlacementPluginFactory`.
+Sub-elements are specific to the implementation.
+
=== The <metrics> Element
The `<metrics>` element in `solr.xml` allows you to customize the metrics reported by Solr.
diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/replica-placement-plugins.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/replica-placement-plugins.adoc
index 4556574ea51..0ce84efaf68 100644
--- a/solr/solr-ref-guide/modules/configuration-guide/pages/replica-placement-plugins.adoc
+++ b/solr/solr-ref-guide/modules/configuration-guide/pages/replica-placement-plugins.adoc
@@ -25,7 +25,8 @@ It can also enforce additional constraints on operations such as collection or r
In earlier versions of Solr, this functionality was provided using either per-collection rules, or with the autoscaling framework.
== Plugin Configuration
-Replica placement plugin configurations are maintained using the `/cluster/plugin` API.
+Replica placement plugin configurations are configured either xref:configuring-solr-xml.adoc#the-replicaplacementfactory-element[ in the `solr.xml` file], or using the `/cluster/plugin` API.
+Any plugin configured in `solr.xml` will be used as long as no replica placement plugin is defined as a cluster plugin.
There can be only one cluster-wide plugin configuration at a time, and it uses a pre-defined plugin name: `.placement-plugin`.
There are several placement plugins included in the Solr distribution.