blob: cdaf70fb453fff8f25910e11f98b1ee6529be7c0 [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.handler.admin;
import static org.apache.solr.client.solrj.response.RequestStatusState.COMPLETED;
import static org.apache.solr.client.solrj.response.RequestStatusState.FAILED;
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.CollectionHandlingUtils.CREATE_NODE_SET;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.CREATE_NODE_SET_EMPTY;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.CREATE_NODE_SET_SHUFFLE;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.NUM_SLICES;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.ONLY_ACTIVE_NODES;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.ONLY_IF_DOWN;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.SHARDS_PROP;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.SHARD_UNIQUE;
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.cloud.ZkStateReader.COLLECTION_PROP;
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_VALUE_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.PULL_REPLICAS;
import static org.apache.solr.common.cloud.ZkStateReader.REPLICATION_FACTOR;
import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_TYPE;
import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.TLOG_REPLICAS;
import static org.apache.solr.common.params.CollectionAdminParams.ALIAS;
import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION;
import static org.apache.solr.common.params.CollectionAdminParams.COLL_CONF;
import static org.apache.solr.common.params.CollectionAdminParams.COUNT_PROP;
import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
import static org.apache.solr.common.params.CollectionAdminParams.PER_REPLICA_STATE;
import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_NAME;
import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_PREFIX;
import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_VALUE;
import static org.apache.solr.common.params.CollectionAdminParams.SKIP_NODE_ASSIGNMENT;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDREPLICA;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDREPLICAPROP;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDROLE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.ALIASPROP;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.BACKUP;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.BALANCESHARDUNIQUE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.CLUSTERPROP;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.CLUSTERSTATUS;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.COLLECTIONPROP;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.COLSTATUS;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.CREATE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.CREATEALIAS;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.CREATESHARD;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.CREATESNAPSHOT;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETEALIAS;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETEBACKUP;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETENODE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETEREPLICA;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETEREPLICAPROP;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETESHARD;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETESNAPSHOT;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETESTATUS;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DISTRIBUTEDAPIPROCESSING;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.FORCELEADER;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.LIST;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.LISTALIASES;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.LISTBACKUP;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.LISTSNAPSHOTS;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.MIGRATE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.MOCK_COLL_TASK;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.MODIFYCOLLECTION;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.MOVEREPLICA;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.OVERSEERSTATUS;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.REBALANCELEADERS;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.REINDEXCOLLECTION;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.RELOAD;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.REMOVEROLE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.RENAME;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.REPLACENODE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.REQUESTSTATUS;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.RESTORE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.SPLITSHARD;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.SYNCSHARD;
import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
import static org.apache.solr.common.params.CommonAdminParams.IN_PLACE_MOVE;
import static org.apache.solr.common.params.CommonAdminParams.NUM_SUB_SHARDS;
import static org.apache.solr.common.params.CommonAdminParams.SPLIT_BY_PREFIX;
import static org.apache.solr.common.params.CommonAdminParams.SPLIT_FUZZ;
import static org.apache.solr.common.params.CommonAdminParams.SPLIT_METHOD;
import static org.apache.solr.common.params.CommonAdminParams.WAIT_FOR_FINAL_STATE;
import static org.apache.solr.common.params.CommonParams.NAME;
import static org.apache.solr.common.params.CommonParams.TIMING;
import static org.apache.solr.common.params.CommonParams.VALUE_LONG;
import static org.apache.solr.common.params.CoreAdminParams.BACKUP_ID;
import static org.apache.solr.common.params.CoreAdminParams.BACKUP_LOCATION;
import static org.apache.solr.common.params.CoreAdminParams.BACKUP_PURGE_UNUSED;
import static org.apache.solr.common.params.CoreAdminParams.BACKUP_REPOSITORY;
import static org.apache.solr.common.params.CoreAdminParams.DATA_DIR;
import static org.apache.solr.common.params.CoreAdminParams.DELETE_DATA_DIR;
import static org.apache.solr.common.params.CoreAdminParams.DELETE_INDEX;
import static org.apache.solr.common.params.CoreAdminParams.DELETE_INSTANCE_DIR;
import static org.apache.solr.common.params.CoreAdminParams.INSTANCE_DIR;
import static org.apache.solr.common.params.CoreAdminParams.MAX_NUM_BACKUP_POINTS;
import static org.apache.solr.common.params.CoreAdminParams.ULOG_DIR;
import static org.apache.solr.common.params.ShardParams._ROUTE_;
import static org.apache.solr.common.util.StrUtils.formatString;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.impl.HttpSolrClient.Builder;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.CoreAdminRequest.RequestSyncShard;
import org.apache.solr.client.solrj.response.RequestStatusState;
import org.apache.solr.client.solrj.util.SolrIdentifierValidator;
import org.apache.solr.cloud.OverseerSolrResponse;
import org.apache.solr.cloud.OverseerSolrResponseSerializer;
import org.apache.solr.cloud.OverseerTaskQueue;
import org.apache.solr.cloud.OverseerTaskQueue.QueueEvent;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.cloud.ZkController.NotInClusterStateException;
import org.apache.solr.cloud.ZkShardTerms;
import org.apache.solr.cloud.api.collections.DistributedCollectionConfigSetCommandRunner;
import org.apache.solr.cloud.api.collections.ReindexCollectionCmd;
import org.apache.solr.cloud.api.collections.RoutedAlias;
import org.apache.solr.cloud.overseer.SliceMutator;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.Aliases;
import org.apache.solr.common.cloud.ClusterProperties;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.CollectionProperties;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.DocCollection.CollectionStateProps;
import org.apache.solr.common.cloud.ImplicitDocRouter;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Replica.State;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.ZkCmdExecutor;
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.apache.solr.common.params.CollectionParams;
import org.apache.solr.common.params.CollectionParams.CollectionAction;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.Pair;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.Utils;
import org.apache.solr.core.CloudConfig;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.backup.BackupFilePaths;
import org.apache.solr.core.backup.BackupId;
import org.apache.solr.core.backup.BackupManager;
import org.apache.solr.core.backup.BackupProperties;
import org.apache.solr.core.backup.repository.BackupRepository;
import org.apache.solr.core.snapshots.CollectionSnapshotMetaData;
import org.apache.solr.core.snapshots.SolrSnapshotManager;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.logging.MDCLoggingContext;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.security.AuthorizationContext;
import org.apache.solr.security.PermissionNameProvider;
import org.apache.solr.util.tracing.TraceUtils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CollectionsHandler extends RequestHandlerBase implements PermissionNameProvider {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected final CoreContainer coreContainer;
private final Optional<DistributedCollectionConfigSetCommandRunner>
distributedCollectionConfigSetCommandRunner;
public CollectionsHandler() {
// Unlike most request handlers, CoreContainer initialization
// should happen in the constructor...
this(null);
}
/**
* Overloaded ctor to inject CoreContainer into the handler.
*
* @param coreContainer Core Container of the solr webapp installed.
*/
public CollectionsHandler(final CoreContainer coreContainer) {
this.coreContainer = coreContainer;
distributedCollectionConfigSetCommandRunner =
coreContainer != null
? coreContainer.getDistributedCollectionCommandRunner()
: Optional.empty();
}
@Override
public PermissionNameProvider.Name getPermissionName(AuthorizationContext ctx) {
String action = ctx.getParams().get("action");
if (action == null) return PermissionNameProvider.Name.COLL_READ_PERM;
CollectionParams.CollectionAction collectionAction =
CollectionParams.CollectionAction.get(action);
if (collectionAction == null) return null;
return collectionAction.isWrite
? PermissionNameProvider.Name.COLL_EDIT_PERM
: PermissionNameProvider.Name.COLL_READ_PERM;
}
@Override
public final void init(NamedList<?> args) {}
/**
* The instance of CoreContainer this handler handles. This should be the CoreContainer instance
* that created this handler.
*
* @return a CoreContainer instance
*/
public CoreContainer getCoreContainer() {
return this.coreContainer;
}
protected void copyFromClusterProp(Map<String, Object> props, String prop) throws IOException {
if (props.get(prop) != null) return; // if it's already specified , return
Object defVal =
new ClusterProperties(coreContainer.getZkController().getZkStateReader().getZkClient())
.getClusterProperty(
ImmutableList.of(
CollectionAdminParams.DEFAULTS, CollectionAdminParams.COLLECTION, prop),
null);
if (defVal != null) props.put(prop, String.valueOf(defVal));
}
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
// Make sure the cores is enabled
CoreContainer cores = checkErrors();
// Pick the action
SolrParams params = req.getParams();
String a = params.get(CoreAdminParams.ACTION);
if (a != null) {
CollectionAction action = CollectionAction.get(a);
if (action == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown action: " + a);
}
final String collection = params.get(COLLECTION);
MDCLoggingContext.setCollection(collection);
TraceUtils.setDbInstance(req, collection);
CollectionOperation operation = CollectionOperation.get(action);
if (log.isDebugEnabled()) {
log.debug(
"Invoked Collection Action: {} with params {}", action.toLower(), req.getParamString());
}
invokeAction(req, rsp, cores, action, operation);
} else {
throw new SolrException(ErrorCode.BAD_REQUEST, "action is a required param");
}
rsp.setHttpCaching(false);
}
protected CoreContainer checkErrors() {
CoreContainer cores = getCoreContainer();
if (cores == null) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Core container instance missing");
}
// Make sure that the core is ZKAware
if (!cores.isZooKeeperAware()) {
throw new SolrException(
ErrorCode.BAD_REQUEST, "Solr instance is not running in SolrCloud mode.");
}
return cores;
}
@SuppressWarnings({"unchecked"})
void invokeAction(
SolrQueryRequest req,
SolrQueryResponse rsp,
CoreContainer cores,
CollectionAction action,
CollectionOperation operation)
throws Exception {
if (!coreContainer.isZooKeeperAware()) {
throw new SolrException(
BAD_REQUEST, "Invalid request. collections can be accessed only in SolrCloud mode");
}
Map<String, Object> props = operation.execute(req, rsp, this);
if (props == null) {
return;
}
String asyncId = req.getParams().get(ASYNC);
if (asyncId != null) {
props.put(ASYNC, asyncId);
}
props.put(QUEUE_OPERATION, operation.action.toLower());
ZkNodeProps zkProps = new ZkNodeProps(props);
final SolrResponse overseerResponse;
overseerResponse = submitCollectionApiCommand(zkProps, operation.action, operation.timeOut);
rsp.getValues().addAll(overseerResponse.getResponse());
Exception exp = overseerResponse.getException();
if (exp != null) {
rsp.setException(exp);
}
// Even if Overseer does wait for the collection to be created, it sees a different cluster
// state than this node, so this wait is required to make sure the local node Zookeeper watches
// fired and now see the collection.
if (action.equals(CollectionAction.CREATE) && asyncId == null) {
if (rsp.getException() == null) {
waitForActiveCollection(zkProps.getStr(NAME), cores, overseerResponse);
}
}
}
static final Set<String> KNOWN_ROLES = ImmutableSet.of("overseer");
public static long DEFAULT_COLLECTION_OP_TIMEOUT = 180 * 1000;
public SolrResponse submitCollectionApiCommand(ZkNodeProps m, CollectionAction action)
throws KeeperException, InterruptedException {
return submitCollectionApiCommand(m, action, DEFAULT_COLLECTION_OP_TIMEOUT);
}
public SolrResponse submitCollectionApiCommand(
ZkNodeProps m, CollectionAction action, long timeout)
throws KeeperException, InterruptedException {
// Collection API messages are either sent to Overseer and processed there, or processed
// locally. Distributing Collection API implies we're also distributing Cluster State Updates.
// Indeed collection creation with non distributed cluster state updates requires for "Per
// Replica States" that the Collection API be running on Overseer, which means that it is not
// possible to distributed Collection API while keeping cluster state updates on Overseer. See
// the call to CollectionCommandContext.submitIntraProcessMessage() in
// CreateCollectionCmd.call() which can only be done if the Collection API command runs on the
// same JVM as the Overseer based cluster state update... The configuration handling includes
// these checks to not allow distributing collection API without distributing cluster state
// updates (but the other way around is ok). See constructor of CloudConfig.
if (distributedCollectionConfigSetCommandRunner.isPresent()) {
return distributedCollectionConfigSetCommandRunner
.get()
.runCollectionCommand(m, action, timeout);
} else { // Sending the Collection API message to Overseer via a Zookeeper queue
String operation = m.getStr(QUEUE_OPERATION);
if (operation == null) {
throw new SolrException(ErrorCode.BAD_REQUEST, "missing key " + QUEUE_OPERATION);
}
if (m.get(ASYNC) != null) {
String asyncId = m.getStr(ASYNC);
NamedList<Object> r = new NamedList<>();
if (coreContainer.getZkController().claimAsyncId(asyncId)) {
boolean success = false;
try {
coreContainer.getZkController().getOverseerCollectionQueue().offer(Utils.toJSON(m));
success = true;
} finally {
if (!success) {
try {
coreContainer.getZkController().clearAsyncId(asyncId);
} catch (Exception e) {
// let the original exception bubble up
log.error("Unable to release async ID={}", asyncId, e);
SolrZkClient.checkInterrupted(e);
}
}
}
} else {
r.add("error", "Task with the same requestid already exists. (" + asyncId + ")");
}
r.add(CoreAdminParams.REQUESTID, m.get(ASYNC));
return new OverseerSolrResponse(r);
}
long time = System.nanoTime();
QueueEvent event =
coreContainer
.getZkController()
.getOverseerCollectionQueue()
.offer(Utils.toJSON(m), timeout);
if (event.getBytes() != null) {
return OverseerSolrResponseSerializer.deserialize(event.getBytes());
} else {
if (System.nanoTime() - time
>= TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS)) {
throw new SolrException(
ErrorCode.SERVER_ERROR,
operation + " the collection time out:" + timeout / 1000 + "s");
} else if (event.getWatchedEvent() != null) {
throw new SolrException(
ErrorCode.SERVER_ERROR,
operation
+ " the collection error [Watcher fired on path: "
+ event.getWatchedEvent().getPath()
+ " state: "
+ event.getWatchedEvent().getState()
+ " type "
+ event.getWatchedEvent().getType()
+ "]");
} else {
throw new SolrException(
ErrorCode.SERVER_ERROR, operation + " the collection unknown case");
}
}
}
}
private boolean overseerCollectionQueueContains(String asyncId)
throws KeeperException, InterruptedException {
OverseerTaskQueue collectionQueue =
coreContainer.getZkController().getOverseerCollectionQueue();
return collectionQueue.containsTaskWithRequestId(ASYNC, asyncId);
}
/**
* Copy prefixed params into a map. There must only be one value for these parameters.
*
* @param params The source of params from which copies should be made
* @param props The map into which param names and values should be copied as keys and values
* respectively
* @param prefix The prefix to select.
* @return the map supplied in the props parameter, modified to contain the prefixed params.
*/
private static Map<String, Object> copyPropertiesWithPrefix(
SolrParams params, Map<String, Object> props, String prefix) {
Iterator<String> iter = params.getParameterNamesIterator();
while (iter.hasNext()) {
String param = iter.next();
if (param.startsWith(prefix)) {
final String[] values = params.getParams(param);
if (values.length != 1) {
throw new SolrException(
BAD_REQUEST, "Only one value can be present for parameter " + param);
}
props.put(param, values[0]);
}
}
return props;
}
public static ModifiableSolrParams params(String... params) {
ModifiableSolrParams msp = new ModifiableSolrParams();
for (int i = 0; i < params.length; i += 2) {
msp.add(params[i], params[i + 1]);
}
return msp;
}
//////////////////////// SolrInfoMBeans methods //////////////////////
@Override
public String getDescription() {
return "Manage SolrCloud Collections";
}
@Override
public Category getCategory() {
return Category.ADMIN;
}
private static void createSysConfigSet(CoreContainer coreContainer)
throws KeeperException, InterruptedException {
SolrZkClient zk = coreContainer.getZkController().getZkStateReader().getZkClient();
ZkCmdExecutor cmdExecutor = new ZkCmdExecutor(zk.getZkClientTimeout());
cmdExecutor.ensureExists(ZkStateReader.CONFIGS_ZKNODE, zk);
cmdExecutor.ensureExists(
ZkStateReader.CONFIGS_ZKNODE + "/" + CollectionAdminParams.SYSTEM_COLL, zk);
try {
String path =
ZkStateReader.CONFIGS_ZKNODE + "/" + CollectionAdminParams.SYSTEM_COLL + "/schema.xml";
byte[] data =
IOUtils.toByteArray(
CollectionsHandler.class.getResourceAsStream("/SystemCollectionSchema.xml"));
assert data != null && data.length > 0;
cmdExecutor.ensureExists(path, data, CreateMode.PERSISTENT, zk);
path =
ZkStateReader.CONFIGS_ZKNODE
+ "/"
+ CollectionAdminParams.SYSTEM_COLL
+ "/solrconfig.xml";
data =
IOUtils.toByteArray(
CollectionsHandler.class.getResourceAsStream("/SystemCollectionSolrConfig.xml"));
assert data != null && data.length > 0;
cmdExecutor.ensureExists(path, data, CreateMode.PERSISTENT, zk);
} catch (IOException e) {
throw new SolrException(ErrorCode.SERVER_ERROR, e);
}
}
private static void addStatusToResponse(
NamedList<Object> results, RequestStatusState state, String msg) {
SimpleOrderedMap<String> status = new SimpleOrderedMap<>();
status.add("state", state.getKey());
status.add("msg", msg);
results.add("status", status);
}
public enum CollectionOperation implements CollectionOp {
CREATE_OP(
CREATE,
(req, rsp, h) -> {
Map<String, Object> props = copy(req.getParams().required(), null, NAME);
props.put("fromApi", "true");
copy(
req.getParams(),
props,
REPLICATION_FACTOR,
COLL_CONF,
NUM_SLICES,
CREATE_NODE_SET,
CREATE_NODE_SET_SHUFFLE,
SHARDS_PROP,
PULL_REPLICAS,
TLOG_REPLICAS,
NRT_REPLICAS,
WAIT_FOR_FINAL_STATE,
PER_REPLICA_STATE,
ALIAS);
if (props.get(REPLICATION_FACTOR) != null && props.get(NRT_REPLICAS) != null) {
// TODO: Remove this in 8.0 . Keep this for SolrJ client back-compat. See SOLR-11676 for
// more details
int replicationFactor = Integer.parseInt((String) props.get(REPLICATION_FACTOR));
int nrtReplicas = Integer.parseInt((String) props.get(NRT_REPLICAS));
if (replicationFactor != nrtReplicas) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"Cannot specify both replicationFactor and nrtReplicas as they mean the same thing");
}
}
if (props.get(REPLICATION_FACTOR) != null) {
props.put(NRT_REPLICAS, props.get(REPLICATION_FACTOR));
} else if (props.get(NRT_REPLICAS) != null) {
props.put(REPLICATION_FACTOR, props.get(NRT_REPLICAS));
}
final String collectionName =
SolrIdentifierValidator.validateCollectionName((String) props.get(NAME));
final String shardsParam = (String) props.get(SHARDS_PROP);
if (StringUtils.isNotEmpty(shardsParam)) {
verifyShardsParam(shardsParam);
}
if (CollectionAdminParams.SYSTEM_COLL.equals(collectionName)) {
// We must always create a .system collection with only a single shard
props.put(NUM_SLICES, 1);
props.remove(SHARDS_PROP);
createSysConfigSet(h.coreContainer);
}
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, PROPERTY_PREFIX);
return copyPropertiesWithPrefix(req.getParams(), props, "router.");
}),
@SuppressWarnings({"unchecked"})
COLSTATUS_OP(
COLSTATUS,
(req, rsp, h) -> {
Map<String, Object> props =
copy(
req.getParams(),
null,
COLLECTION_PROP,
ColStatus.CORE_INFO_PROP,
ColStatus.SEGMENTS_PROP,
ColStatus.FIELD_INFO_PROP,
ColStatus.RAW_SIZE_PROP,
ColStatus.RAW_SIZE_SUMMARY_PROP,
ColStatus.RAW_SIZE_DETAILS_PROP,
ColStatus.RAW_SIZE_SAMPLING_PERCENT_PROP,
ColStatus.SIZE_INFO_PROP);
new ColStatus(
h.coreContainer.getSolrClientCache(),
h.coreContainer.getZkController().getZkStateReader().getClusterState(),
new ZkNodeProps(props))
.getColStatus(rsp.getValues());
return null;
}),
DELETE_OP(
DELETE,
(req, rsp, h) -> {
Map<String, Object> map = copy(req.getParams().required(), null, NAME);
return copy(req.getParams(), map, FOLLOW_ALIASES);
}),
// XXX should this command support followAliases?
RELOAD_OP(
RELOAD,
(req, rsp, h) -> {
Map<String, Object> map = copy(req.getParams().required(), null, NAME);
return copy(req.getParams(), map);
}),
RENAME_OP(
RENAME,
(req, rsp, h) -> {
Map<String, Object> map =
copy(req.getParams().required(), null, NAME, CollectionAdminParams.TARGET);
return copy(req.getParams(), map, FOLLOW_ALIASES);
}),
REINDEXCOLLECTION_OP(
REINDEXCOLLECTION,
(req, rsp, h) -> {
Map<String, Object> m = copy(req.getParams().required(), null, NAME);
copy(
req.getParams(),
m,
ReindexCollectionCmd.COMMAND,
ReindexCollectionCmd.REMOVE_SOURCE,
ReindexCollectionCmd.TARGET,
ZkStateReader.CONFIGNAME_PROP,
NUM_SLICES,
NRT_REPLICAS,
PULL_REPLICAS,
TLOG_REPLICAS,
REPLICATION_FACTOR,
CREATE_NODE_SET,
CREATE_NODE_SET_SHUFFLE,
"shards",
CommonParams.ROWS,
CommonParams.Q,
CommonParams.FL,
FOLLOW_ALIASES);
if (req.getParams().get("collection." + ZkStateReader.CONFIGNAME_PROP) != null) {
m.put(
ZkStateReader.CONFIGNAME_PROP,
req.getParams().get("collection." + ZkStateReader.CONFIGNAME_PROP));
}
copyPropertiesWithPrefix(req.getParams(), m, "router.");
return m;
}),
SYNCSHARD_OP(
SYNCSHARD,
(req, rsp, h) -> {
String extCollection = req.getParams().required().get("collection");
String collection =
h.coreContainer
.getZkController()
.getZkStateReader()
.getAliases()
.resolveSimpleAlias(extCollection);
String shard = req.getParams().required().get("shard");
ClusterState clusterState = h.coreContainer.getZkController().getClusterState();
DocCollection docCollection = clusterState.getCollection(collection);
ZkNodeProps leaderProps = docCollection.getLeader(shard);
ZkCoreNodeProps nodeProps = new ZkCoreNodeProps(leaderProps);
try (HttpSolrClient client =
new Builder(nodeProps.getBaseUrl())
.withConnectionTimeout(15000)
.withSocketTimeout(60000)
.build()) {
RequestSyncShard reqSyncShard = new RequestSyncShard();
reqSyncShard.setCollection(collection);
reqSyncShard.setShard(shard);
reqSyncShard.setCoreName(nodeProps.getCoreName());
client.request(reqSyncShard);
}
return null;
}),
CREATEALIAS_OP(
CREATEALIAS,
(req, rsp, h) -> {
String alias = req.getParams().get(NAME);
SolrIdentifierValidator.validateAliasName(alias);
String collections = req.getParams().get("collections");
RoutedAlias routedAlias = null;
Exception ex = null;
HashMap<String, Object> possiblyModifiedParams = new HashMap<>();
try {
// note that RA specific validation occurs here.
req.getParams().toMap(possiblyModifiedParams);
@SuppressWarnings({"unchecked", "rawtypes"})
// This is awful because RoutedAlias lies about what types it wants
Map<String, String> temp = (Map<String, String>) (Map) possiblyModifiedParams;
routedAlias = RoutedAlias.fromProps(alias, temp);
} catch (SolrException e) {
// we'll throw this later if we are in fact creating a routed alias.
ex = e;
}
ModifiableSolrParams finalParams = new ModifiableSolrParams();
for (Map.Entry<String, Object> entry : possiblyModifiedParams.entrySet()) {
if (entry.getValue().getClass().isArray()) {
// v2 api hits this case
for (Object o : (Object[]) entry.getValue()) {
finalParams.add(entry.getKey(), o.toString());
}
} else {
finalParams.add(entry.getKey(), entry.getValue().toString());
}
}
if (collections != null) {
if (routedAlias != null) {
throw new SolrException(
BAD_REQUEST, "Collections cannot be specified when creating a routed alias.");
} else {
//////////////////////////////////////
// Regular alias creation indicated //
//////////////////////////////////////
return copy(finalParams.required(), null, NAME, "collections");
}
}
/////////////////////////////////////////////////
// We are creating a routed alias from here on //
/////////////////////////////////////////////////
// If our prior creation attempt had issues expose them now.
if (ex != null) {
throw ex;
}
// Now filter out just the parameters we care about from the request
assert routedAlias != null;
Map<String, Object> result = copy(finalParams, null, routedAlias.getRequiredParams());
copy(finalParams, result, routedAlias.getOptionalParams());
ModifiableSolrParams createCollParams = new ModifiableSolrParams(); // without prefix
// add to result params that start with "create-collection.".
// Additionally, save these without the prefix to createCollParams
for (Map.Entry<String, String[]> entry : finalParams) {
final String p = entry.getKey();
if (p.startsWith(CREATE_COLLECTION_PREFIX)) {
// This is what SolrParams#getAll(Map, Collection)} does
final String[] v = entry.getValue();
if (v.length == 1) {
result.put(p, v[0]);
} else {
result.put(p, v);
}
createCollParams.set(p.substring(CREATE_COLLECTION_PREFIX.length()), v);
}
}
// Verify that the create-collection prefix'ed params appear to be valid.
if (createCollParams.get(NAME) != null) {
throw new SolrException(
BAD_REQUEST,
"routed aliases calculate names for their "
+ "dependent collections, you cannot specify the name.");
}
if (createCollParams.get(COLL_CONF) == null) {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST, "We require an explicit " + COLL_CONF);
}
// note: could insist on a config name here as well.... or wait to throw at overseer
createCollParams.add(NAME, "TMP_name_TMP_name_TMP"); // just to pass validation
CREATE_OP.execute(
new LocalSolrQueryRequest(null, createCollParams), rsp, h); // ignore results
return result;
}),
DELETEALIAS_OP(DELETEALIAS, (req, rsp, h) -> copy(req.getParams().required(), null, NAME)),
/**
* Change properties for an alias (use CREATEALIAS_OP to change the actual value of the alias)
*/
ALIASPROP_OP(
ALIASPROP,
(req, rsp, h) -> {
Map<String, Object> params = copy(req.getParams().required(), null, NAME);
// Note: success/no-op in the event of no properties supplied is intentional. Keeps code
// simple and one less case for api-callers to check for.
return convertPrefixToMap(req.getParams(), params, "property");
}),
/** List the aliases and associated properties. */
@SuppressWarnings({"unchecked"})
LISTALIASES_OP(
LISTALIASES,
(req, rsp, h) -> {
ZkStateReader zkStateReader = h.coreContainer.getZkController().getZkStateReader();
// if someone calls listAliases, lets ensure we return an up to date response
zkStateReader.aliasesManager.update();
Aliases aliases = zkStateReader.getAliases();
if (aliases != null) {
// the aliases themselves...
rsp.getValues().add("aliases", aliases.getCollectionAliasMap());
// Any properties for the above aliases.
Map<String, Map<String, String>> meta = new LinkedHashMap<>();
for (String alias : aliases.getCollectionAliasListMap().keySet()) {
Map<String, String> collectionAliasProperties =
aliases.getCollectionAliasProperties(alias);
if (!collectionAliasProperties.isEmpty()) {
meta.put(alias, collectionAliasProperties);
}
}
rsp.getValues().add("properties", meta);
}
return null;
}),
SPLITSHARD_OP(
SPLITSHARD,
DEFAULT_COLLECTION_OP_TIMEOUT * 5,
(req, rsp, h) -> {
String name = req.getParams().required().get(COLLECTION_PROP);
// TODO : add support for multiple shards
String shard = req.getParams().get(SHARD_ID_PROP);
String rangesStr = req.getParams().get(CoreAdminParams.RANGES);
String splitKey = req.getParams().get("split.key");
String numSubShards = req.getParams().get(NUM_SUB_SHARDS);
String fuzz = req.getParams().get(SPLIT_FUZZ);
if (splitKey == null && shard == null) {
throw new SolrException(
ErrorCode.BAD_REQUEST, "At least one of shard, or split.key should be specified.");
}
if (splitKey != null && shard != null) {
throw new SolrException(
ErrorCode.BAD_REQUEST, "Only one of 'shard' or 'split.key' should be specified");
}
if (splitKey != null && rangesStr != null) {
throw new SolrException(
ErrorCode.BAD_REQUEST, "Only one of 'ranges' or 'split.key' should be specified");
}
if (numSubShards != null && (splitKey != null || rangesStr != null)) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"numSubShards can not be specified with split.key or ranges parameters");
}
if (fuzz != null && (splitKey != null || rangesStr != null)) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"fuzz can not be specified with split.key or ranges parameters");
}
Map<String, Object> map =
copy(
req.getParams(),
null,
COLLECTION_PROP,
SHARD_ID_PROP,
"split.key",
CoreAdminParams.RANGES,
WAIT_FOR_FINAL_STATE,
TIMING,
SPLIT_METHOD,
NUM_SUB_SHARDS,
SPLIT_FUZZ,
SPLIT_BY_PREFIX,
FOLLOW_ALIASES);
return copyPropertiesWithPrefix(req.getParams(), map, PROPERTY_PREFIX);
}),
DELETESHARD_OP(
DELETESHARD,
(req, rsp, h) -> {
Map<String, Object> map =
copy(req.getParams().required(), null, COLLECTION_PROP, SHARD_ID_PROP);
copy(
req.getParams(),
map,
DELETE_INDEX,
DELETE_DATA_DIR,
DELETE_INSTANCE_DIR,
FOLLOW_ALIASES);
return map;
}),
FORCELEADER_OP(
FORCELEADER,
(req, rsp, h) -> {
forceLeaderElection(req, h);
return null;
}),
CREATESHARD_OP(
CREATESHARD,
(req, rsp, h) -> {
Map<String, Object> map =
copy(req.getParams().required(), null, COLLECTION_PROP, SHARD_ID_PROP);
ClusterState clusterState = h.coreContainer.getZkController().getClusterState();
final String newShardName =
SolrIdentifierValidator.validateShardName(req.getParams().get(SHARD_ID_PROP));
boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false);
String extCollectionName = req.getParams().get(COLLECTION_PROP);
String collectionName =
followAliases
? h.coreContainer
.getZkController()
.getZkStateReader()
.getAliases()
.resolveSimpleAlias(extCollectionName)
: extCollectionName;
if (!ImplicitDocRouter.NAME.equals(
((Map<?, ?>)
clusterState
.getCollection(collectionName)
.get(CollectionStateProps.DOC_ROUTER))
.get(NAME)))
throw new SolrException(
ErrorCode.BAD_REQUEST, "shards can be added only to 'implicit' collections");
copy(
req.getParams(),
map,
REPLICATION_FACTOR,
NRT_REPLICAS,
TLOG_REPLICAS,
PULL_REPLICAS,
CREATE_NODE_SET,
WAIT_FOR_FINAL_STATE,
FOLLOW_ALIASES);
return copyPropertiesWithPrefix(req.getParams(), map, PROPERTY_PREFIX);
}),
DELETEREPLICA_OP(
DELETEREPLICA,
(req, rsp, h) -> {
Map<String, Object> map = copy(req.getParams().required(), null, COLLECTION_PROP);
return copy(
req.getParams(),
map,
DELETE_INDEX,
DELETE_DATA_DIR,
DELETE_INSTANCE_DIR,
COUNT_PROP,
REPLICA_PROP,
SHARD_ID_PROP,
ONLY_IF_DOWN,
FOLLOW_ALIASES);
}),
MIGRATE_OP(
MIGRATE,
(req, rsp, h) -> {
Map<String, Object> map =
copy(
req.getParams().required(),
null,
COLLECTION_PROP,
"split.key",
"target.collection");
return copy(req.getParams(), map, "forward.timeout", FOLLOW_ALIASES);
}),
ADDROLE_OP(
ADDROLE,
(req, rsp, h) -> {
Map<String, Object> map = copy(req.getParams().required(), null, "role", "node");
if (!KNOWN_ROLES.contains(map.get("role")))
throw new SolrException(
ErrorCode.BAD_REQUEST, "Unknown role. Supported roles are ," + KNOWN_ROLES);
return map;
}),
REMOVEROLE_OP(
REMOVEROLE,
(req, rsp, h) -> {
Map<String, Object> map = copy(req.getParams().required(), null, "role", "node");
if (!KNOWN_ROLES.contains(map.get("role")))
throw new SolrException(
ErrorCode.BAD_REQUEST, "Unknown role. Supported roles are ," + KNOWN_ROLES);
return map;
}),
CLUSTERPROP_OP(
CLUSTERPROP,
(req, rsp, h) -> {
String name = req.getParams().required().get(NAME);
String val = req.getParams().get(VALUE_LONG);
ClusterProperties cp =
new ClusterProperties(h.coreContainer.getZkController().getZkClient());
cp.setClusterProperty(name, val);
return null;
}),
COLLECTIONPROP_OP(
COLLECTIONPROP,
(req, rsp, h) -> {
String extCollection = req.getParams().required().get(NAME);
String collection =
h.coreContainer
.getZkController()
.getZkStateReader()
.getAliases()
.resolveSimpleAlias(extCollection);
String name = req.getParams().required().get(PROPERTY_NAME);
String val = req.getParams().get(PROPERTY_VALUE);
CollectionProperties cp =
new CollectionProperties(h.coreContainer.getZkController().getZkClient());
cp.setCollectionProperty(collection, name, val);
return null;
}),
@SuppressWarnings({"unchecked"})
REQUESTSTATUS_OP(
REQUESTSTATUS,
(req, rsp, h) -> {
req.getParams().required().check(REQUESTID);
final CoreContainer coreContainer = h.coreContainer;
final String requestId = req.getParams().get(REQUESTID);
final ZkController zkController = coreContainer.getZkController();
final NamedList<Object> status = new NamedList<>();
if (coreContainer.getDistributedCollectionCommandRunner().isEmpty()) {
if (zkController.getOverseerCompletedMap().contains(requestId)) {
final byte[] mapEntry = zkController.getOverseerCompletedMap().get(requestId);
rsp.getValues()
.addAll(OverseerSolrResponseSerializer.deserialize(mapEntry).getResponse());
addStatusToResponse(
status, COMPLETED, "found [" + requestId + "] in completed tasks");
} else if (zkController.getOverseerFailureMap().contains(requestId)) {
final byte[] mapEntry = zkController.getOverseerFailureMap().get(requestId);
rsp.getValues()
.addAll(OverseerSolrResponseSerializer.deserialize(mapEntry).getResponse());
addStatusToResponse(status, FAILED, "found [" + requestId + "] in failed tasks");
} else if (zkController.getOverseerRunningMap().contains(requestId)) {
addStatusToResponse(status, RUNNING, "found [" + requestId + "] in running tasks");
} else if (h.overseerCollectionQueueContains(requestId)) {
addStatusToResponse(
status, SUBMITTED, "found [" + requestId + "] in submitted tasks");
} else {
addStatusToResponse(
status, NOT_FOUND, "Did not find [" + requestId + "] in any tasks queue");
}
} else {
Pair<RequestStatusState, OverseerSolrResponse> sr =
coreContainer
.getDistributedCollectionCommandRunner()
.get()
.getAsyncTaskRequestStatus(requestId);
final String message;
switch (sr.first()) {
case COMPLETED:
message = "found [" + requestId + "] in completed tasks";
rsp.getValues().addAll(sr.second().getResponse());
break;
case FAILED:
message = "found [" + requestId + "] in failed tasks";
rsp.getValues().addAll(sr.second().getResponse());
break;
case RUNNING:
message = "found [" + requestId + "] in running tasks";
break;
case SUBMITTED:
message = "found [" + requestId + "] in submitted tasks";
break;
default:
message = "Did not find [" + requestId + "] in any tasks queue";
}
addStatusToResponse(status, sr.first(), message);
}
rsp.getValues().addAll(status);
return null;
}),
DELETESTATUS_OP(
DELETESTATUS,
new CollectionOp() {
@SuppressWarnings("unchecked")
@Override
public Map<String, Object> execute(
SolrQueryRequest req, SolrQueryResponse rsp, CollectionsHandler h) throws Exception {
final CoreContainer coreContainer = h.coreContainer;
final String requestId = req.getParams().get(REQUESTID);
final ZkController zkController = coreContainer.getZkController();
Boolean flush = req.getParams().getBool(CollectionAdminParams.FLUSH, false);
if (requestId == null && !flush) {
throw new SolrException(
ErrorCode.BAD_REQUEST, "Either requestid or flush parameter must be specified.");
}
if (requestId != null && flush) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"Both requestid and flush parameters can not be specified together.");
}
if (coreContainer.getDistributedCollectionCommandRunner().isEmpty()) {
if (flush) {
Collection<String> completed = zkController.getOverseerCompletedMap().keys();
Collection<String> failed = zkController.getOverseerFailureMap().keys();
for (String asyncId : completed) {
zkController.getOverseerCompletedMap().remove(asyncId);
zkController.clearAsyncId(asyncId);
}
for (String asyncId : failed) {
zkController.getOverseerFailureMap().remove(asyncId);
zkController.clearAsyncId(asyncId);
}
rsp.getValues()
.add("status", "successfully cleared stored collection api responses");
} else {
// Request to cleanup
if (zkController.getOverseerCompletedMap().remove(requestId)) {
zkController.clearAsyncId(requestId);
rsp.getValues()
.add(
"status", "successfully removed stored response for [" + requestId + "]");
} else if (zkController.getOverseerFailureMap().remove(requestId)) {
zkController.clearAsyncId(requestId);
rsp.getValues()
.add(
"status", "successfully removed stored response for [" + requestId + "]");
} else {
rsp.getValues()
.add("status", "[" + requestId + "] not found in stored responses");
// Don't call zkController.clearAsyncId for this, since it could be a
// running/pending task
}
}
} else {
if (flush) {
coreContainer.getDistributedCollectionCommandRunner().get().deleteAllAsyncIds();
rsp.getValues()
.add("status", "successfully cleared stored collection api responses");
} else {
if (coreContainer
.getDistributedCollectionCommandRunner()
.get()
.deleteSingleAsyncId(requestId)) {
rsp.getValues()
.add(
"status", "successfully removed stored response for [" + requestId + "]");
} else {
rsp.getValues()
.add("status", "[" + requestId + "] not found in stored responses");
}
}
}
return null;
}
}),
ADDREPLICA_OP(
ADDREPLICA,
(req, rsp, h) -> {
Map<String, Object> props =
copy(
req.getParams(),
null,
COLLECTION_PROP,
"node",
SHARD_ID_PROP,
_ROUTE_,
CoreAdminParams.NAME,
INSTANCE_DIR,
DATA_DIR,
ULOG_DIR,
REPLICA_TYPE,
WAIT_FOR_FINAL_STATE,
NRT_REPLICAS,
TLOG_REPLICAS,
PULL_REPLICAS,
CREATE_NODE_SET,
FOLLOW_ALIASES,
SKIP_NODE_ASSIGNMENT);
return copyPropertiesWithPrefix(req.getParams(), props, PROPERTY_PREFIX);
}),
OVERSEERSTATUS_OP(OVERSEERSTATUS, (req, rsp, h) -> new LinkedHashMap<>()),
@SuppressWarnings({"unchecked"})
DISTRIBUTEDAPIPROCESSING_OP(
DISTRIBUTEDAPIPROCESSING,
(req, rsp, h) -> {
NamedList<Object> results = new NamedList<>();
boolean isDistributedApi =
h.coreContainer.getDistributedCollectionCommandRunner().isPresent();
results.add("isDistributedApi", isDistributedApi);
rsp.getValues().addAll(results);
return null;
}),
/** Handle list collection request. Do list collection request to zk host */
@SuppressWarnings({"unchecked"})
LIST_OP(
LIST,
(req, rsp, h) -> {
NamedList<Object> results = new NamedList<>();
Map<String, DocCollection> collections =
h.coreContainer
.getZkController()
.getZkStateReader()
.getClusterState()
.getCollectionsMap();
List<String> collectionList = new ArrayList<>(collections.keySet());
Collections.sort(collectionList);
// XXX should we add aliases here?
results.add("collections", collectionList);
SolrResponse response = new OverseerSolrResponse(results);
rsp.getValues().addAll(response.getResponse());
return null;
}),
/**
* Handle cluster status request. Can return status per specific collection/shard or per all
* collections.
*/
CLUSTERSTATUS_OP(
CLUSTERSTATUS,
(req, rsp, h) -> {
Map<String, Object> all =
copy(req.getParams(), null, COLLECTION_PROP, SHARD_ID_PROP, _ROUTE_, "prs");
new ClusterStatus(
h.coreContainer.getZkController().getZkStateReader(), new ZkNodeProps(all))
.getClusterStatus(rsp.getValues());
return null;
}),
ADDREPLICAPROP_OP(
ADDREPLICAPROP,
(req, rsp, h) -> {
Map<String, Object> map =
copy(
req.getParams().required(),
null,
COLLECTION_PROP,
PROPERTY_PROP,
SHARD_ID_PROP,
REPLICA_PROP,
PROPERTY_VALUE_PROP);
copy(req.getParams(), map, SHARD_UNIQUE);
String property = (String) map.get(PROPERTY_PROP);
if (!property.startsWith(PROPERTY_PREFIX)) {
property = PROPERTY_PREFIX + property;
}
boolean uniquePerSlice = Boolean.parseBoolean((String) map.get(SHARD_UNIQUE));
// Check if we're trying to set a property with parameters that allow us to set the
// property on multiple replicas in a slice on properties that are known to only be
// one-per-slice and error out if so.
if (StringUtils.isNotBlank((String) map.get(SHARD_UNIQUE))
&& SliceMutator.SLICE_UNIQUE_BOOLEAN_PROPERTIES.contains(
property.toLowerCase(Locale.ROOT))
&& uniquePerSlice == false) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"Overseer replica property command received for property "
+ property
+ " with the "
+ SHARD_UNIQUE
+ " parameter set to something other than 'true'. No action taken.");
}
return map;
}),
// XXX should this command support followAliases?
DELETEREPLICAPROP_OP(
DELETEREPLICAPROP,
(req, rsp, h) -> {
Map<String, Object> map =
copy(
req.getParams().required(),
null,
COLLECTION_PROP,
PROPERTY_PROP,
SHARD_ID_PROP,
REPLICA_PROP);
return copy(req.getParams(), map, PROPERTY_PROP);
}),
// XXX should this command support followAliases?
BALANCESHARDUNIQUE_OP(
BALANCESHARDUNIQUE,
(req, rsp, h) -> {
Map<String, Object> map =
copy(req.getParams().required(), null, COLLECTION_PROP, 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, PROPERTY_PREFIX)) {
prop = PROPERTY_PREFIX + prop;
}
if (!shardUnique && !SliceMutator.SLICE_UNIQUE_BOOLEAN_PROPERTIES.contains(prop)) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"Balancing properties amongst replicas in a slice requires that"
+ " the property be pre-defined as a unique property (e.g. 'preferredLeader') or that 'shardUnique' be set to 'true'. "
+ " Property: "
+ prop
+ " shardUnique: "
+ shardUnique);
}
return copy(req.getParams(), map, ONLY_ACTIVE_NODES, SHARD_UNIQUE);
}),
REBALANCELEADERS_OP(
REBALANCELEADERS,
(req, rsp, h) -> {
new RebalanceLeaders(req, rsp, h).execute();
return null;
}),
// 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, PROPERTY_PREFIX);
if (m.isEmpty()) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
formatString(
"no supported values provided {0}",
CollectionAdminRequest.MODIFIABLE_COLLECTION_PROPERTIES.toString()));
}
copy(req.getParams().required(), m, COLLECTION_PROP);
for (Map.Entry<String, Object> entry : m.entrySet()) {
String prop = entry.getKey();
if ("".equals(entry.getValue())) {
// set to an empty string is equivalent to removing the property, see SOLR-12507
entry.setValue(null);
}
DocCollection.verifyProp(m, prop);
}
if (m.get(REPLICATION_FACTOR) != null) {
m.put(NRT_REPLICAS, m.get(REPLICATION_FACTOR));
}
return m;
}),
BACKUP_OP(
BACKUP,
(req, rsp, h) -> {
req.getParams().required().check(NAME, COLLECTION_PROP);
final String extCollectionName = req.getParams().get(COLLECTION_PROP);
final boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false);
final String collectionName =
followAliases
? h.coreContainer
.getZkController()
.getZkStateReader()
.getAliases()
.resolveSimpleAlias(extCollectionName)
: extCollectionName;
final ClusterState clusterState = h.coreContainer.getZkController().getClusterState();
if (!clusterState.hasCollection(collectionName)) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"Collection '" + collectionName + "' does not exist, no action taken.");
}
CoreContainer cc = h.coreContainer;
String repo = req.getParams().get(CoreAdminParams.BACKUP_REPOSITORY);
BackupRepository repository = cc.newBackupRepository(repo);
String location =
repository.getBackupLocation(req.getParams().get(CoreAdminParams.BACKUP_LOCATION));
if (location == null) {
// Refresh the cluster property file to make sure the value set for location is the
// latest. Check if the location is specified in the cluster property.
location =
new ClusterProperties(h.coreContainer.getZkController().getZkClient())
.getClusterProperty(CoreAdminParams.BACKUP_LOCATION, null);
if (location == null) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"'location' is not specified as a query"
+ " parameter or as a default repository property or as a cluster property.");
}
}
boolean incremental = req.getParams().getBool(CoreAdminParams.BACKUP_INCREMENTAL, true);
// Check if the specified location is valid for this repository.
final URI uri = repository.createDirectoryURI(location);
try {
if (!repository.exists(uri)) {
throw new SolrException(
ErrorCode.SERVER_ERROR, "specified location " + uri + " does not exist.");
}
} catch (IOException ex) {
throw new SolrException(
ErrorCode.SERVER_ERROR,
"Failed to check the existence of " + uri + ". Is it valid?",
ex);
}
String strategy =
req.getParams()
.get(
CollectionAdminParams.INDEX_BACKUP_STRATEGY,
CollectionAdminParams.COPY_FILES_STRATEGY);
if (!CollectionAdminParams.INDEX_BACKUP_STRATEGIES.contains(strategy)) {
throw new SolrException(
ErrorCode.BAD_REQUEST, "Unknown index backup strategy " + strategy);
}
Map<String, Object> params =
copy(
req.getParams(),
null,
NAME,
COLLECTION_PROP,
FOLLOW_ALIASES,
CoreAdminParams.COMMIT_NAME,
CoreAdminParams.MAX_NUM_BACKUP_POINTS);
params.put(CoreAdminParams.BACKUP_LOCATION, location);
if (repo != null) {
params.put(CoreAdminParams.BACKUP_REPOSITORY, repo);
}
params.put(CollectionAdminParams.INDEX_BACKUP_STRATEGY, strategy);
params.put(CoreAdminParams.BACKUP_INCREMENTAL, incremental);
return params;
}),
RESTORE_OP(
RESTORE,
(req, rsp, h) -> {
req.getParams().required().check(NAME, COLLECTION_PROP);
final String collectionName =
SolrIdentifierValidator.validateCollectionName(req.getParams().get(COLLECTION_PROP));
if (h.coreContainer
.getZkController()
.getZkStateReader()
.getAliases()
.hasAlias(collectionName)) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"Collection '" + collectionName + "' is an existing alias, no action taken.");
}
final CoreContainer cc = h.coreContainer;
final String repo = req.getParams().get(CoreAdminParams.BACKUP_REPOSITORY);
final BackupRepository repository = cc.newBackupRepository(repo);
String location =
repository.getBackupLocation(req.getParams().get(CoreAdminParams.BACKUP_LOCATION));
if (location == null) {
// Refresh the cluster property file to make sure the value set for location is the
// latest. Check if the location is specified in the cluster property.
location =
new ClusterProperties(h.coreContainer.getZkController().getZkClient())
.getClusterProperty("location", null);
if (location == null) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"'location' is not specified as a query"
+ " parameter or as a default repository property or as a cluster property.");
}
}
// Check if the specified location is valid for this repository.
final URI uri = repository.createDirectoryURI(location);
try {
if (!repository.exists(uri)) {
throw new SolrException(
ErrorCode.SERVER_ERROR, "specified location " + uri + " does not exist.");
}
} catch (IOException ex) {
throw new SolrException(
ErrorCode.SERVER_ERROR,
"Failed to check the existence of " + uri + ". Is it valid?",
ex);
}
final String createNodeArg = req.getParams().get(CREATE_NODE_SET);
if (CREATE_NODE_SET_EMPTY.equals(createNodeArg)) {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST,
"Cannot restore with a CREATE_NODE_SET of CREATE_NODE_SET_EMPTY.");
}
if (req.getParams().get(NRT_REPLICAS) != null
&& req.getParams().get(REPLICATION_FACTOR) != null) {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST,
"Cannot set both replicationFactor and nrtReplicas as they mean the same thing");
}
final Map<String, Object> params = copy(req.getParams(), null, NAME, COLLECTION_PROP);
params.put(CoreAdminParams.BACKUP_LOCATION, location);
if (repo != null) {
params.put(CoreAdminParams.BACKUP_REPOSITORY, repo);
}
// from CREATE_OP:
copy(
req.getParams(),
params,
COLL_CONF,
REPLICATION_FACTOR,
NRT_REPLICAS,
TLOG_REPLICAS,
PULL_REPLICAS,
CREATE_NODE_SET,
CREATE_NODE_SET_SHUFFLE,
BACKUP_ID);
copyPropertiesWithPrefix(req.getParams(), params, PROPERTY_PREFIX);
return params;
}),
DELETEBACKUP_OP(
DELETEBACKUP,
(req, rsp, h) -> {
req.getParams().required().check(NAME);
CoreContainer cc = h.coreContainer;
String repo = req.getParams().get(CoreAdminParams.BACKUP_REPOSITORY);
try (BackupRepository repository = cc.newBackupRepository(repo)) {
String location =
repository.getBackupLocation(req.getParams().get(CoreAdminParams.BACKUP_LOCATION));
if (location == null) {
// Refresh the cluster property file to make sure the value set for location is the
// latest. Check if the location is specified in the cluster property.
location =
new ClusterProperties(h.coreContainer.getZkController().getZkClient())
.getClusterProperty("location", null);
if (location == null) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"'location' is not specified as a query"
+ " parameter or as a default repository property or as a cluster property.");
}
}
// Check if the specified location is valid for this repository.
URI uri = repository.createDirectoryURI(location);
try {
if (!repository.exists(uri)) {
throw new SolrException(
ErrorCode.BAD_REQUEST, "specified location " + uri + " does not exist.");
}
} catch (IOException ex) {
throw new SolrException(
ErrorCode.SERVER_ERROR,
"Failed to check the existence of " + uri + ". Is it valid?",
ex);
}
int deletionModesProvided = 0;
if (req.getParams().get(MAX_NUM_BACKUP_POINTS) != null) deletionModesProvided++;
if (req.getParams().get(BACKUP_PURGE_UNUSED) != null) deletionModesProvided++;
if (req.getParams().get(BACKUP_ID) != null) deletionModesProvided++;
if (deletionModesProvided != 1) {
throw new SolrException(
BAD_REQUEST,
String.format(
Locale.ROOT,
"Exactly one of %s, %s, and %s parameters must be provided",
MAX_NUM_BACKUP_POINTS,
BACKUP_PURGE_UNUSED,
BACKUP_ID));
}
final Map<String, Object> params =
copy(
req.getParams(),
null,
NAME,
BACKUP_REPOSITORY,
BACKUP_LOCATION,
BACKUP_ID,
MAX_NUM_BACKUP_POINTS,
BACKUP_PURGE_UNUSED);
params.put(BACKUP_LOCATION, location);
if (repo != null) {
params.put(CoreAdminParams.BACKUP_REPOSITORY, repo);
}
return params;
}
}),
LISTBACKUP_OP(
LISTBACKUP,
(req, rsp, h) -> {
req.getParams().required().check(NAME);
CoreContainer cc = h.coreContainer;
String repo = req.getParams().get(CoreAdminParams.BACKUP_REPOSITORY);
try (BackupRepository repository = cc.newBackupRepository(repo)) {
String location =
repository.getBackupLocation(req.getParams().get(CoreAdminParams.BACKUP_LOCATION));
if (location == null) {
// Refresh the cluster property file to make sure the value set for location is the
// latest. Check if the location is specified in the cluster property.
location =
new ClusterProperties(h.coreContainer.getZkController().getZkClient())
.getClusterProperty(CoreAdminParams.BACKUP_LOCATION, null);
if (location == null) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"'location' is not specified as a query"
+ " parameter or as a default repository property or as a cluster property.");
}
}
String backupName = req.getParams().get(NAME);
final URI locationURI = repository.createDirectoryURI(location);
try {
if (!repository.exists(locationURI)) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"specified location " + locationURI + " does not exist.");
}
} catch (IOException ex) {
throw new SolrException(
ErrorCode.SERVER_ERROR,
"Failed to check the existence of " + locationURI + ". Is it valid?",
ex);
}
URI backupLocation =
BackupFilePaths.buildExistingBackupLocationURI(repository, locationURI, backupName);
if (repository.exists(
repository.resolve(backupLocation, BackupManager.TRADITIONAL_BACKUP_PROPS_FILE))) {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST,
"The backup name ["
+ backupName
+ "] at "
+ "location ["
+ location
+ "] holds a non-incremental (legacy) backup, but "
+ "backup-listing is only supported on incremental backups");
}
String[] subFiles = repository.listAllOrEmpty(backupLocation);
List<BackupId> propsFiles = BackupFilePaths.findAllBackupIdsFromFileListing(subFiles);
NamedList<Object> results = new NamedList<>();
ArrayList<Map<Object, Object>> backups = new ArrayList<>();
String collectionName = null;
for (BackupId backupId : propsFiles) {
BackupProperties properties =
BackupProperties.readFrom(
repository, backupLocation, BackupFilePaths.getBackupPropsName(backupId));
if (collectionName == null) {
collectionName = properties.getCollection();
results.add(BackupManager.COLLECTION_NAME_PROP, collectionName);
}
Map<Object, Object> details = properties.getDetails();
details.put("backupId", backupId.id);
backups.add(details);
}
results.add("backups", backups);
SolrResponse response = new OverseerSolrResponse(results);
rsp.getValues().addAll(response.getResponse());
return null;
}
}),
CREATESNAPSHOT_OP(
CREATESNAPSHOT,
(req, rsp, h) -> {
req.getParams().required().check(COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
String extCollectionName = req.getParams().get(COLLECTION_PROP);
boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false);
String collectionName =
followAliases
? h.coreContainer
.getZkController()
.getZkStateReader()
.getAliases()
.resolveSimpleAlias(extCollectionName)
: extCollectionName;
String commitName = req.getParams().get(CoreAdminParams.COMMIT_NAME);
ClusterState clusterState = h.coreContainer.getZkController().getClusterState();
if (!clusterState.hasCollection(collectionName)) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"Collection '" + collectionName + "' does not exist, no action taken.");
}
SolrZkClient client = h.coreContainer.getZkController().getZkClient();
if (SolrSnapshotManager.snapshotExists(client, collectionName, commitName)) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"Snapshot with name '"
+ commitName
+ "' already exists for collection '"
+ collectionName
+ "', no action taken.");
}
Map<String, Object> params =
copy(
req.getParams(),
null,
COLLECTION_PROP,
FOLLOW_ALIASES,
CoreAdminParams.COMMIT_NAME);
return params;
}),
DELETESNAPSHOT_OP(
DELETESNAPSHOT,
(req, rsp, h) -> {
req.getParams().required().check(COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
String extCollectionName = req.getParams().get(COLLECTION_PROP);
String collectionName =
h.coreContainer
.getZkController()
.getZkStateReader()
.getAliases()
.resolveSimpleAlias(extCollectionName);
ClusterState clusterState = h.coreContainer.getZkController().getClusterState();
if (!clusterState.hasCollection(collectionName)) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"Collection '" + collectionName + "' does not exist, no action taken.");
}
Map<String, Object> params =
copy(
req.getParams(),
null,
COLLECTION_PROP,
FOLLOW_ALIASES,
CoreAdminParams.COMMIT_NAME);
return params;
}),
LISTSNAPSHOTS_OP(
LISTSNAPSHOTS,
(req, rsp, h) -> {
req.getParams().required().check(COLLECTION_PROP);
String extCollectionName = req.getParams().get(COLLECTION_PROP);
String collectionName =
h.coreContainer
.getZkController()
.getZkStateReader()
.getAliases()
.resolveSimpleAlias(extCollectionName);
ClusterState clusterState = h.coreContainer.getZkController().getClusterState();
if (!clusterState.hasCollection(collectionName)) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"Collection '" + collectionName + "' does not exist, no action taken.");
}
NamedList<Object> snapshots = new NamedList<Object>();
SolrZkClient client = h.coreContainer.getZkController().getZkClient();
Collection<CollectionSnapshotMetaData> m =
SolrSnapshotManager.listSnapshots(client, collectionName);
for (CollectionSnapshotMetaData meta : m) {
snapshots.add(meta.getName(), meta.toNamedList());
}
rsp.add(SolrSnapshotManager.SNAPSHOTS_INFO, snapshots);
return null;
}),
REPLACENODE_OP(
REPLACENODE,
(req, rsp, h) -> {
return copy(
req.getParams(),
null,
"source", // legacy
"target", // legacy
WAIT_FOR_FINAL_STATE,
CollectionParams.SOURCE_NODE,
CollectionParams.TARGET_NODE);
}),
MOVEREPLICA_OP(
MOVEREPLICA,
(req, rsp, h) -> {
Map<String, Object> map = copy(req.getParams().required(), null, COLLECTION_PROP);
return copy(
req.getParams(),
map,
CollectionParams.FROM_NODE,
CollectionParams.SOURCE_NODE,
CollectionParams.TARGET_NODE,
WAIT_FOR_FINAL_STATE,
IN_PLACE_MOVE,
"replica",
"shard",
FOLLOW_ALIASES);
}),
DELETENODE_OP(DELETENODE, (req, rsp, h) -> copy(req.getParams().required(), null, "node")),
MOCK_COLL_TASK_OP(
MOCK_COLL_TASK,
(req, rsp, h) -> {
Map<String, Object> map = copy(req.getParams().required(), null, COLLECTION_PROP);
return copy(req.getParams(), map, "sleep");
});
/**
* Places all prefixed properties in the sink map (or a new map) using the prefix as the key and
* a map of all prefixed properties as the value. The sub-map keys have the prefix removed.
*
* @param params The solr params from which to extract prefixed properties.
* @param sink The map to add the properties too.
* @param prefix The prefix to identify properties to be extracted
* @return The sink map, or a new map if the sink map was null
*/
private static Map<String, Object> convertPrefixToMap(
SolrParams params, Map<String, Object> sink, String prefix) {
Map<String, Object> result = new LinkedHashMap<>();
Iterator<String> iter = params.getParameterNamesIterator();
while (iter.hasNext()) {
String param = iter.next();
if (param.startsWith(prefix)) {
result.put(param.substring(prefix.length() + 1), params.get(param));
}
}
if (sink == null) {
sink = new LinkedHashMap<>();
}
sink.put(prefix, result);
return sink;
}
public final CollectionOp fun;
CollectionAction action;
long timeOut;
CollectionOperation(CollectionAction action, CollectionOp fun) {
this(action, DEFAULT_COLLECTION_OP_TIMEOUT, fun);
}
CollectionOperation(CollectionAction action, long timeOut, CollectionOp fun) {
this.action = action;
this.timeOut = timeOut;
this.fun = fun;
}
public static CollectionOperation get(CollectionAction action) {
for (CollectionOperation op : values()) {
if (op.action == action) return op;
}
throw new SolrException(ErrorCode.SERVER_ERROR, "No such action " + action);
}
@Override
public Map<String, Object> execute(
SolrQueryRequest req, SolrQueryResponse rsp, CollectionsHandler h) throws Exception {
return fun.execute(req, rsp, h);
}
}
private static void forceLeaderElection(SolrQueryRequest req, CollectionsHandler handler) {
ZkController zkController = handler.coreContainer.getZkController();
ClusterState clusterState = zkController.getClusterState();
String extCollectionName = req.getParams().required().get(COLLECTION_PROP);
String collectionName =
zkController.zkStateReader.getAliases().resolveSimpleAlias(extCollectionName);
String sliceId = req.getParams().required().get(SHARD_ID_PROP);
log.info("Force leader invoked, state: {}", clusterState);
DocCollection collection = clusterState.getCollection(collectionName);
Slice slice = collection.getSlice(sliceId);
if (slice == null) {
throw new SolrException(
ErrorCode.BAD_REQUEST,
"No shard with name " + sliceId + " exists for collection " + collectionName);
}
try (ZkShardTerms zkShardTerms =
new ZkShardTerms(collectionName, slice.getName(), zkController.getZkClient())) {
// if an active replica is the leader, then all is fine already
Replica leader = slice.getLeader();
if (leader != null && leader.getState() == State.ACTIVE) {
throw new SolrException(
ErrorCode.SERVER_ERROR,
"The shard already has an active leader. Force leader is not applicable. State: "
+ slice);
}
final Set<String> liveNodes = clusterState.getLiveNodes();
List<Replica> liveReplicas =
slice.getReplicas().stream()
.filter(rep -> liveNodes.contains(rep.getNodeName()))
.collect(Collectors.toList());
boolean shouldIncreaseReplicaTerms =
liveReplicas.stream()
.noneMatch(
rep ->
zkShardTerms.registered(rep.getName())
&& zkShardTerms.canBecomeLeader(rep.getName()));
// we won't increase replica's terms if exist a live replica with term equals to leader
if (shouldIncreaseReplicaTerms) {
// TODO only increase terms of replicas less out-of-sync
liveReplicas.stream()
.filter(rep -> zkShardTerms.registered(rep.getName()))
// TODO should this all be done at once instead of increasing each replica individually?
.forEach(rep -> zkShardTerms.setTermEqualsToLeader(rep.getName()));
}
// Wait till we have an active leader
boolean success = false;
for (int i = 0; i < 9; i++) {
Thread.sleep(5000);
clusterState = handler.coreContainer.getZkController().getClusterState();
collection = clusterState.getCollection(collectionName);
slice = collection.getSlice(sliceId);
if (slice.getLeader() != null && slice.getLeader().getState() == State.ACTIVE) {
success = true;
break;
}
log.warn(
"Force leader attempt {}. Waiting 5 secs for an active leader. State of the slice: {}",
(i + 1),
slice); // nowarn
}
if (success) {
log.info(
"Successfully issued FORCELEADER command for collection: {}, shard: {}",
collectionName,
sliceId);
} else {
log.info(
"Couldn't successfully force leader, collection: {}, shard: {}. Cluster state: {}",
collectionName,
sliceId,
clusterState);
}
} catch (SolrException e) {
throw e;
} catch (Exception e) {
throw new SolrException(
ErrorCode.SERVER_ERROR,
"Error executing FORCELEADER operation for collection: "
+ collectionName
+ " shard: "
+ sliceId,
e);
}
}
public static void waitForActiveCollection(
String collectionName, CoreContainer cc, SolrResponse createCollResponse)
throws KeeperException, InterruptedException {
if (createCollResponse.getResponse().get("exception") != null) {
// the main called failed, don't wait
if (log.isInfoEnabled()) {
log.info(
"Not waiting for active collection due to exception: {}",
createCollResponse.getResponse().get("exception"));
}
return;
}
int replicaFailCount;
if (createCollResponse.getResponse().get("failure") != null) {
replicaFailCount = ((NamedList) createCollResponse.getResponse().get("failure")).size();
} else {
replicaFailCount = 0;
}
CloudConfig ccfg = cc.getConfig().getCloudConfig();
Integer seconds = ccfg.getCreateCollectionWaitTimeTillActive();
Boolean checkLeaderOnly = ccfg.isCreateCollectionCheckLeaderActive();
if (log.isInfoEnabled()) {
log.info(
"Wait for new collection to be active for at most {} seconds. Check all shard {}",
seconds,
(checkLeaderOnly ? "leaders" : "replicas"));
}
try {
cc.getZkController()
.getZkStateReader()
.waitForState(
collectionName,
seconds,
TimeUnit.SECONDS,
(n, c) -> {
if (c == null) {
// the collection was not created, don't wait
return true;
}
if (c.getSlices() != null) {
Collection<Slice> shards = c.getSlices();
int replicaNotAliveCnt = 0;
for (Slice shard : shards) {
Collection<Replica> replicas;
if (!checkLeaderOnly) replicas = shard.getReplicas();
else {
replicas = new ArrayList<Replica>();
replicas.add(shard.getLeader());
}
for (Replica replica : replicas) {
State state = replica.getState();
if (log.isDebugEnabled()) {
log.debug(
"Checking replica status, collection={} replica={} state={}",
collectionName,
replica.getCoreUrl(),
state);
}
if (!n.contains(replica.getNodeName())
|| !state.equals(Replica.State.ACTIVE)) {
replicaNotAliveCnt++;
return false;
}
}
}
return (replicaNotAliveCnt == 0) || (replicaNotAliveCnt <= replicaFailCount);
}
return false;
});
} catch (TimeoutException | InterruptedException e) {
String error =
"Timeout waiting for active collection " + collectionName + " with timeout=" + seconds;
throw new NotInClusterStateException(ErrorCode.SERVER_ERROR, error);
}
}
private static void verifyShardsParam(String shardsParam) {
for (String shard : shardsParam.split(",")) {
SolrIdentifierValidator.validateShardName(shard);
}
}
interface CollectionOp {
Map<String, Object> execute(SolrQueryRequest req, SolrQueryResponse rsp, CollectionsHandler h)
throws Exception;
}
@Override
public Boolean registerV2() {
return Boolean.TRUE;
}
// These "copy" methods were once SolrParams.getAll but were moved here as there is no universal
// way that a SolrParams can be represented in a Map; there are various choices.
/** Copy all params to the given map or if the given map is null create a new one */
static Map<String, Object> copy(
SolrParams source, Map<String, Object> sink, Collection<String> paramNames) {
if (sink == null) sink = new LinkedHashMap<>();
for (String param : paramNames) {
String[] v = source.getParams(param);
if (v != null && v.length > 0) {
if (v.length == 1) {
sink.put(param, v[0]);
} else {
sink.put(param, v);
}
}
}
return sink;
}
/** Copy all params to the given map or if the given map is null create a new one */
static Map<String, Object> copy(
SolrParams source, Map<String, Object> sink, String... paramNames) {
return copy(
source, sink, paramNames == null ? Collections.emptyList() : Arrays.asList(paramNames));
}
}