blob: e6130a3034232ae519f3307f00847d8ba36ee7d2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.cluster.placement.impl;
import java.util.HashMap;
import java.util.Map;
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
import org.apache.solr.cluster.placement.PlacementPlugin;
import org.apache.solr.cluster.placement.PlacementPluginConfig;
import org.apache.solr.cluster.placement.PlacementPluginFactory;
import org.apache.solr.cluster.placement.plugins.SamplePluginAffinityReplicaPlacement;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.Utils;
/**
* <p>This concrete class is implementing the config as visible by the placement plugins and contains the code transforming the
* plugin configuration (currently stored in {@code clusterprops.json} into a strongly typed abstraction (that will not
* change if internally plugin configuration is moved to some other place).</p>
*
* <p>This class also contains the (static) code dealing with instantiating the plugin factory config (it is config, even though
* of a slightly different type). This code is not accessed by the plugin code but used from the
* {@link org.apache.solr.cloud.api.collections.Assign} class.</p>
*/
public class PlacementPluginConfigImpl implements PlacementPluginConfig {
/**
* The key in {@code clusterprops.json} under which the plugin factory and the plugin configuration are defined.
*/
final public static String PLACEMENT_PLUGIN_CONFIG_KEY = "placement-plugin";
/** Name of the property containing the factory class */
final public static String CONFIG_CLASS = "class";
// Separating configs into typed maps based on the element names in solr.xml
private final Map<String, String> stringConfigs;
private final Map<String, Long> longConfigs;
private final Map<String, Boolean> boolConfigs;
private final Map<String, Double> doubleConfigs;
private PlacementPluginConfigImpl(Map<String, String> stringConfigs,
Map<String, Long> longConfigs,
Map<String, Boolean> boolConfigs,
Map<String, Double> doubleConfigs) {
this.stringConfigs = stringConfigs;
this.longConfigs = longConfigs;
this.boolConfigs = boolConfigs;
this.doubleConfigs = doubleConfigs;
}
@Override
public String getStringConfig(String configName) {
return stringConfigs.get(configName);
}
@Override
public String getStringConfig(String configName, String defaultValue) {
String retval = stringConfigs.get(configName);
return retval != null ? retval : defaultValue;
}
@Override
public Boolean getBooleanConfig(String configName) {
return boolConfigs.get(configName);
}
@Override
public Boolean getBooleanConfig(String configName, boolean defaultValue) {
Boolean retval = boolConfigs.get(configName);
return retval != null ? retval : defaultValue;
}
@Override
public Long getLongConfig(String configName) {
return longConfigs.get(configName);
}
@Override
public Long getLongConfig(String configName, long defaultValue) {
Long retval = longConfigs.get(configName);
return retval != null ? retval : defaultValue;
}
@Override
public Double getDoubleConfig(String configName) {
return doubleConfigs.get(configName);
}
@Override
public Double getDoubleConfig(String configName, double defaultValue) {
Double retval = doubleConfigs.get(configName);
return retval != null ? retval : defaultValue;
}
/**
* <p>Parses the {@link Map} obtained as the value for key {@link #PLACEMENT_PLUGIN_CONFIG_KEY} from
* the {@code clusterprops.json} configuration {@link Map} (obtained by calling
* {@link org.apache.solr.client.solrj.impl.ClusterStateProvider#getClusterProperties()}) and translates it into a
* configuration consumable by the plugin (and that will not change as Solr changes internally how and where it stores
* configuration).</p>
*
* <p>Configuration properties {@code class} and {@code name} are reserved: for defining the plugin factory class and
* a human readable plugin name. All other properties are plugin specific.</p>
*
* <p>See configuration example and how-to in {@link SamplePluginAffinityReplicaPlacement}.</p>
*/
static PlacementPluginConfig createConfigFromProperties(Map<String, Object> pluginConfig) {
final Map<String, String> stringConfigs = new HashMap<>();
final Map<String, Long> longConfigs = new HashMap<>();
final Map<String, Boolean> boolConfigs = new HashMap<>();
final Map<String, Double> doubleConfigs = new HashMap<>();
for (Map.Entry<String, Object> e : pluginConfig.entrySet()) {
String key = e.getKey();
if (CONFIG_CLASS.equals(key)) {
continue;
}
if (key == null) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Missing config name attribute in parameter of " + PLACEMENT_PLUGIN_CONFIG_KEY);
}
Object value = e.getValue();
if (value == null) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Missing config value for parameter " + key + " of " + PLACEMENT_PLUGIN_CONFIG_KEY);
}
if (value instanceof String) {
stringConfigs.put(key, (String) value);
} else if (value instanceof Long) {
longConfigs.put(key, (Long) value);
} else if (value instanceof Boolean) {
boolConfigs.put(key, (Boolean) value);
} else if (value instanceof Double) {
doubleConfigs.put(key, (Double) value);
} else {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unsupported config type " + value.getClass().getName() +
" for parameter " + key + " of " + PLACEMENT_PLUGIN_CONFIG_KEY);
}
}
return new PlacementPluginConfigImpl(stringConfigs, longConfigs, boolConfigs, doubleConfigs);
}
/**
* <p>This is where the plugin configuration is being read (from wherever in Solr it lives, and this will likely change with time),
* a {@link org.apache.solr.cluster.placement.PlacementPluginFactory} (as configured) instantiated and a plugin instance
* created from this factory.</p>
*
* <p>The initial implementation you see here is crude! the configuration is read anew each time and the factory class
* as well as the plugin class instantiated each time.
* This has to be changed once the code is accepted overall, to register a listener that is notified when the configuration
* changes (see {@link org.apache.solr.common.cloud.ZkStateReader#registerClusterPropertiesListener})
* and that will either create a new instance of the plugin with new configuration using the existing factory (if the factory
* class has not changed - we need to keep track of this one) of create a new factory altogether (then a new plugin instance).</p>
*/
@SuppressWarnings({"unchecked"})
public static PlacementPlugin getPlacementPlugin(SolrCloudManager solrCloudManager) {
Map<String, Object> props = solrCloudManager.getClusterStateProvider().getClusterProperties();
Map<String, Object> pluginConfigMap = (Map<String, Object>) props.get(PLACEMENT_PLUGIN_CONFIG_KEY);
if (pluginConfigMap == null) {
return null;
}
String pluginFactoryClassName = (String) pluginConfigMap.get(CONFIG_CLASS);
// Get the configured plugin factory class. Is there a way to load a resource in Solr without being in the context of
// CoreContainer? Here the placement code is unrelated to the presence of cores (and one can imagine it running on
// specialized nodes not having a CoreContainer). I guess the loading code below is not totally satisfying (although
// it's not the only place in Solr doing it that way), but I didn't find more satisfying alternatives. Open to suggestions.
PlacementPluginFactory placementPluginFactory;
try {
Class<? extends PlacementPluginFactory> factoryClazz =
Class.forName(pluginFactoryClassName, true, PlacementPluginConfigImpl.class.getClassLoader())
.asSubclass(PlacementPluginFactory.class);
placementPluginFactory = factoryClazz.getConstructor().newInstance(); // no args constructor - that's why we introduced a factory...
} catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to instantiate placement-plugin factory: " +
Utils.toJSONString(pluginConfigMap) + " please review /clusterprops.json config for " + PLACEMENT_PLUGIN_CONFIG_KEY, e);
}
// Translate the config from the properties where they are defined into the abstraction seen by the plugin
PlacementPluginConfig pluginConfig = createConfigFromProperties(pluginConfigMap);
return placementPluginFactory.createPluginInstance(pluginConfig);
}
}