blob: d00ed09b76614cd21c7723b3b59f3ef2db41e4b0 [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_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.REQUESTID;
import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.SHARD_UNIQUE;
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.COLLECTION;
import static org.apache.solr.common.params.CollectionAdminParams.CREATE_NODE_SET_PARAM;
import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
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.SHARD;
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.INSTALLSHARDDATA;
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.CollectionParams.SOURCE_NODE;
import static org.apache.solr.common.params.CollectionParams.TARGET_NODE;
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_LOCATION;
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.INSTANCE_DIR;
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 java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
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.solr.api.AnnotatedApi;
import org.apache.solr.api.Api;
import org.apache.solr.api.JerseyResource;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrResponse;
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.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.overseer.SliceMutator;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.ClusterProperties;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Replica.State;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.SolrZkClient;
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.RequiredSolrParams;
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.snapshots.CollectionSnapshotMetaData;
import org.apache.solr.core.snapshots.SolrSnapshotManager;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.handler.admin.api.AddReplicaAPI;
import org.apache.solr.handler.admin.api.AddReplicaPropertyAPI;
import org.apache.solr.handler.admin.api.AdminAPIBase;
import org.apache.solr.handler.admin.api.AliasPropertyAPI;
import org.apache.solr.handler.admin.api.BalanceShardUniqueAPI;
import org.apache.solr.handler.admin.api.CollectionPropertyAPI;
import org.apache.solr.handler.admin.api.CollectionStatusAPI;
import org.apache.solr.handler.admin.api.CreateAliasAPI;
import org.apache.solr.handler.admin.api.CreateCollectionAPI;
import org.apache.solr.handler.admin.api.CreateCollectionBackupAPI;
import org.apache.solr.handler.admin.api.CreateCollectionSnapshotAPI;
import org.apache.solr.handler.admin.api.CreateShardAPI;
import org.apache.solr.handler.admin.api.DeleteAliasAPI;
import org.apache.solr.handler.admin.api.DeleteCollectionAPI;
import org.apache.solr.handler.admin.api.DeleteCollectionBackupAPI;
import org.apache.solr.handler.admin.api.DeleteCollectionSnapshotAPI;
import org.apache.solr.handler.admin.api.DeleteNodeAPI;
import org.apache.solr.handler.admin.api.DeleteReplicaAPI;
import org.apache.solr.handler.admin.api.DeleteReplicaPropertyAPI;
import org.apache.solr.handler.admin.api.DeleteShardAPI;
import org.apache.solr.handler.admin.api.ForceLeaderAPI;
import org.apache.solr.handler.admin.api.InstallShardDataAPI;
import org.apache.solr.handler.admin.api.ListAliasesAPI;
import org.apache.solr.handler.admin.api.ListCollectionBackupsAPI;
import org.apache.solr.handler.admin.api.ListCollectionSnapshotsAPI;
import org.apache.solr.handler.admin.api.ListCollectionsAPI;
import org.apache.solr.handler.admin.api.MigrateDocsAPI;
import org.apache.solr.handler.admin.api.ModifyCollectionAPI;
import org.apache.solr.handler.admin.api.MoveReplicaAPI;
import org.apache.solr.handler.admin.api.RebalanceLeadersAPI;
import org.apache.solr.handler.admin.api.ReloadCollectionAPI;
import org.apache.solr.handler.admin.api.RenameCollectionAPI;
import org.apache.solr.handler.admin.api.ReplaceNodeAPI;
import org.apache.solr.handler.admin.api.RestoreCollectionAPI;
import org.apache.solr.handler.admin.api.SplitShardAPI;
import org.apache.solr.handler.admin.api.SyncShardAPI;
import org.apache.solr.handler.api.V2ApiUtils;
import org.apache.solr.jersey.SolrJerseyResponse;
import org.apache.solr.logging.MDCLoggingContext;
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.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;
}
@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) {
throw new SolrException(ErrorCode.BAD_REQUEST, "action is a required param");
}
CollectionAction action = CollectionAction.get(a);
if (action == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown action: " + a);
}
// Initial logging/tracing setup
final String collection = params.get(COLLECTION);
MDCLoggingContext.setCollection(collection);
TraceUtils.setDbInstance(req, collection);
if (log.isDebugEnabled()) {
log.debug(
"Invoked Collection Action: {} with params {}", action.toLower(), req.getParamString());
}
CollectionOperation operation = CollectionOperation.get(action);
invokeAction(req, rsp, cores, action, operation);
rsp.setHttpCaching(false);
}
protected CoreContainer checkErrors() {
CoreContainer cores = getCoreContainer();
AdminAPIBase.validateZooKeeperAwareCoreContainer(cores);
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);
}
}
static final Set<String> KNOWN_ROLES = Set.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 static SolrResponse submitCollectionApiCommand(
CoreContainer coreContainer,
Optional<DistributedCollectionConfigSetCommandRunner>
distributedCollectionConfigSetCommandRunner,
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(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 {
throw new SolrException(
BAD_REQUEST, "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");
}
}
}
}
public SolrResponse submitCollectionApiCommand(
ZkNodeProps m, CollectionAction action, long timeout)
throws KeeperException, InterruptedException {
return submitCollectionApiCommand(
coreContainer, distributedCollectionConfigSetCommandRunner, m, action, timeout);
}
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 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);
}
@SuppressWarnings("ImmutableEnumChecker")
public enum CollectionOperation implements CollectionOp {
CREATE_OP(
CREATE,
(req, rsp, h) -> {
final CreateCollectionAPI.CreateCollectionRequestBody requestBody =
CreateCollectionAPI.CreateCollectionRequestBody.fromV1Params(req.getParams(), true);
final CreateCollectionAPI createApi = new CreateCollectionAPI(h.coreContainer, req, rsp);
final SolrJerseyResponse response = createApi.createCollection(requestBody);
// 'rsp' may be null, as when overseer commands execute CollectionAction impl's directly.
if (rsp != null) {
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response);
}
return null;
}),
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) -> {
final RequiredSolrParams requiredParams = req.getParams().required();
final DeleteCollectionAPI deleteCollectionAPI =
new DeleteCollectionAPI(h.coreContainer, req, rsp);
final SolrJerseyResponse deleteCollResponse =
deleteCollectionAPI.deleteCollection(
requiredParams.get(NAME),
req.getParams().getBool(FOLLOW_ALIASES),
req.getParams().get(ASYNC));
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteCollResponse);
return null;
}),
// 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.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 (SolrClient client =
new Builder(nodeProps.getBaseUrl())
.withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
.withSocketTimeout(60000, TimeUnit.MILLISECONDS)
.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) -> {
final CreateAliasAPI.CreateAliasRequestBody reqBody =
CreateAliasAPI.createFromSolrParams(req.getParams());
final SolrJerseyResponse response =
new CreateAliasAPI(h.coreContainer, req, rsp).createAlias(reqBody);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response);
return null;
}),
DELETEALIAS_OP(
DELETEALIAS,
(req, rsp, h) -> {
final DeleteAliasAPI deleteAliasAPI = new DeleteAliasAPI(h.coreContainer, req, rsp);
final SolrJerseyResponse response =
deleteAliasAPI.deleteAlias(
req.getParams().required().get(NAME), req.getParams().get(ASYNC));
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response);
return null;
}),
/**
* Change properties for an alias (use CREATEALIAS_OP to change the actual value of the alias)
*/
ALIASPROP_OP(
ALIASPROP,
(req, rsp, h) -> {
String name = req.getParams().required().get(NAME);
Map<String, Object> properties = collectToMap(req.getParams(), "property");
AliasPropertyAPI.UpdateAliasPropertiesRequestBody requestBody =
new AliasPropertyAPI.UpdateAliasPropertiesRequestBody();
requestBody.properties = properties;
requestBody.async = req.getParams().get(ASYNC);
final AliasPropertyAPI aliasPropertyAPI = new AliasPropertyAPI(h.coreContainer, req, rsp);
final SolrJerseyResponse getAliasesResponse =
aliasPropertyAPI.updateAliasProperties(name, requestBody);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, getAliasesResponse);
return null;
}),
/** List the aliases and associated properties. */
LISTALIASES_OP(
LISTALIASES,
(req, rsp, h) -> {
final ListAliasesAPI getAliasesAPI = new ListAliasesAPI(h.coreContainer, req, rsp);
final SolrJerseyResponse getAliasesResponse = getAliasesAPI.getAliases();
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, getAliasesResponse);
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,
CREATE_NODE_SET_PARAM);
return copyPropertiesWithPrefix(req.getParams(), map, PROPERTY_PREFIX);
}),
DELETESHARD_OP(
DELETESHARD,
(req, rsp, h) -> {
DeleteShardAPI.invokeWithV1Params(h.coreContainer, req, rsp);
return null;
}),
FORCELEADER_OP(
FORCELEADER,
(req, rsp, h) -> {
forceLeaderElection(req, h);
return null;
}),
CREATESHARD_OP(
CREATESHARD,
(req, rsp, h) -> {
CreateShardAPI.invokeFromV1Params(h.coreContainer, req, rsp);
return null;
}),
DELETEREPLICA_OP(
DELETEREPLICA,
(req, rsp, h) -> {
DeleteReplicaAPI.invokeWithV1Params(h.coreContainer, req, rsp);
return null;
}),
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) -> {
final String collection = req.getParams().required().get(NAME);
final String propName = req.getParams().required().get(PROPERTY_NAME);
final String val = req.getParams().get(PROPERTY_VALUE);
final CollectionPropertyAPI setCollPropApi =
new CollectionPropertyAPI(h.coreContainer, req, rsp);
final SolrJerseyResponse setPropRsp =
(val != null)
? setCollPropApi.createOrUpdateCollectionProperty(
collection,
propName,
new CollectionPropertyAPI.UpdateCollectionPropertyRequestBody(val))
: setCollPropApi.deleteCollectionProperty(collection, propName);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, setPropRsp);
return null;
}),
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() {
@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<>()),
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 */
LIST_OP(
LIST,
(req, rsp, h) -> {
final ListCollectionsAPI listCollectionsAPI =
new ListCollectionsAPI(h.coreContainer, req, rsp);
final SolrJerseyResponse listCollectionsResponse = listCollectionsAPI.listCollections();
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, listCollectionsResponse);
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) -> {
final RequiredSolrParams requiredParams = req.getParams().required();
final AddReplicaPropertyAPI.AddReplicaPropertyRequestBody requestBody =
new AddReplicaPropertyAPI.AddReplicaPropertyRequestBody();
requestBody.value = requiredParams.get(PROPERTY_VALUE_PROP);
requestBody.shardUnique = req.getParams().getBool(SHARD_UNIQUE);
final String propName = requiredParams.get(PROPERTY_PROP);
final String trimmedPropName =
propName.startsWith(PROPERTY_PREFIX)
? propName.substring(PROPERTY_PREFIX.length())
: propName;
final AddReplicaPropertyAPI addReplicaPropertyAPI =
new AddReplicaPropertyAPI(h.coreContainer, req, rsp);
final SolrJerseyResponse addReplicaPropResponse =
addReplicaPropertyAPI.addReplicaProperty(
requiredParams.get(COLLECTION_PROP),
requiredParams.get(SHARD_ID_PROP),
requiredParams.get(REPLICA_PROP),
trimmedPropName,
requestBody);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, addReplicaPropResponse);
return null;
}),
DELETEREPLICAPROP_OP(
DELETEREPLICAPROP,
(req, rsp, h) -> {
final var api = new DeleteReplicaPropertyAPI(h.coreContainer, req, rsp);
final var deleteReplicaPropResponse =
DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, req.getParams());
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteReplicaPropResponse);
return null;
}),
// 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 (!prop.startsWith(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) -> {
final var response =
CreateCollectionBackupAPI.invokeFromV1Params(req, rsp, h.coreContainer);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response);
return null;
}),
RESTORE_OP(
RESTORE,
(req, rsp, h) -> {
final var response = RestoreCollectionAPI.invokeFromV1Params(req, rsp, h.coreContainer);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response);
return null;
}),
INSTALLSHARDDATA_OP(
INSTALLSHARDDATA,
(req, rsp, h) -> {
req.getParams().required().check(COLLECTION, SHARD);
final String collectionName = req.getParams().get(COLLECTION);
final String shardName = req.getParams().get(SHARD);
final InstallShardDataAPI.InstallShardRequestBody reqBody =
new InstallShardDataAPI.InstallShardRequestBody();
reqBody.asyncId = req.getParams().get(ASYNC);
reqBody.repository = req.getParams().get(BACKUP_REPOSITORY);
reqBody.location = req.getParams().get(BACKUP_LOCATION);
final InstallShardDataAPI installApi = new InstallShardDataAPI(h.coreContainer, req, rsp);
final SolrJerseyResponse installResponse =
installApi.installShardData(collectionName, shardName, reqBody);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, installResponse);
return null;
}),
DELETEBACKUP_OP(
DELETEBACKUP,
(req, rsp, h) -> {
DeleteCollectionBackupAPI.invokeFromV1Params(h.coreContainer, req, rsp);
return null;
}),
LISTBACKUP_OP(
LISTBACKUP,
(req, rsp, h) -> {
req.getParams().required().check(NAME);
ListCollectionBackupsAPI.invokeFromV1Params(h.coreContainer, req, rsp);
return null;
}),
CREATESNAPSHOT_OP(
CREATESNAPSHOT,
(req, rsp, h) -> {
req.getParams().required().check(COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
final String extCollectionName = req.getParams().get(COLLECTION_PROP);
final boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false);
final String commitName = req.getParams().get(CoreAdminParams.COMMIT_NAME);
final String asyncId = req.getParams().get(ASYNC);
final CreateCollectionSnapshotAPI createCollectionSnapshotAPI =
new CreateCollectionSnapshotAPI(h.coreContainer, req, rsp);
final CreateCollectionSnapshotAPI.CreateSnapshotRequestBody requestBody =
new CreateCollectionSnapshotAPI.CreateSnapshotRequestBody();
requestBody.followAliases = followAliases;
requestBody.asyncId = asyncId;
final CreateCollectionSnapshotAPI.CreateSnapshotResponse createSnapshotResponse =
createCollectionSnapshotAPI.createSnapshot(
extCollectionName, commitName, requestBody);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, createSnapshotResponse);
return null;
}),
DELETESNAPSHOT_OP(
DELETESNAPSHOT,
(req, rsp, h) -> {
req.getParams().required().check(COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
final String extCollectionName = req.getParams().get(COLLECTION_PROP);
final String commitName = req.getParams().get(CoreAdminParams.COMMIT_NAME);
final boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false);
final String asyncId = req.getParams().get(ASYNC);
final DeleteCollectionSnapshotAPI deleteCollectionSnapshotAPI =
new DeleteCollectionSnapshotAPI(h.coreContainer, req, rsp);
final DeleteCollectionSnapshotAPI.DeleteSnapshotResponse deleteSnapshotResponse =
deleteCollectionSnapshotAPI.deleteSnapshot(
extCollectionName, commitName, followAliases, asyncId);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteSnapshotResponse);
return null;
}),
LISTSNAPSHOTS_OP(
LISTSNAPSHOTS,
(req, rsp, h) -> {
req.getParams().required().check(COLLECTION_PROP);
final ListCollectionSnapshotsAPI listCollectionSnapshotsAPI =
new ListCollectionSnapshotsAPI(h.coreContainer, req, rsp);
final ListCollectionSnapshotsAPI.ListSnapshotsResponse response =
listCollectionSnapshotsAPI.listSnapshots(req.getParams().get(COLLECTION_PROP));
NamedList<Object> snapshots = new NamedList<>();
for (CollectionSnapshotMetaData meta : response.snapshots.values()) {
snapshots.add(meta.getName(), meta.toNamedList());
}
rsp.add(SolrSnapshotManager.SNAPSHOTS_INFO, snapshots);
return null;
}),
REPLACENODE_OP(
REPLACENODE,
(req, rsp, h) -> {
final SolrParams params = req.getParams();
final RequiredSolrParams requiredParams = req.getParams().required();
final ReplaceNodeAPI.ReplaceNodeRequestBody requestBody =
new ReplaceNodeAPI.ReplaceNodeRequestBody();
requestBody.targetNodeName = params.get(TARGET_NODE);
requestBody.waitForFinalState = params.getBool(WAIT_FOR_FINAL_STATE);
requestBody.async = params.get(ASYNC);
final ReplaceNodeAPI replaceNodeAPI = new ReplaceNodeAPI(h.coreContainer, req, rsp);
final SolrJerseyResponse replaceNodeResponse =
replaceNodeAPI.replaceNode(requiredParams.get(SOURCE_NODE), requestBody);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, replaceNodeResponse);
return null;
}),
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,
TARGET_NODE,
WAIT_FOR_FINAL_STATE,
IN_PLACE_MOVE,
"replica",
"shard",
FOLLOW_ALIASES);
}),
DELETENODE_OP(
DELETENODE,
(req, rsp, h) -> {
final DeleteNodeAPI deleteNodeAPI = new DeleteNodeAPI(h.coreContainer, req, rsp);
final SolrJerseyResponse deleteNodeResponse =
DeleteNodeAPI.invokeUsingV1Inputs(deleteNodeAPI, req.getParams());
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteNodeResponse);
return null;
}),
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");
});
/**
* Collects all prefixed properties in a new map. The resulting keys have the prefix removed.
*
* @param params The solr params from which to extract prefixed properties.
* @param prefix The prefix to identify properties to be extracted
* @return a map with collected properties
*/
private static Map<String, Object> collectToMap(SolrParams params, String prefix) {
Map<String, Object> sink = new LinkedHashMap<>();
Iterator<String> iter = params.getParameterNamesIterator();
while (iter.hasNext()) {
String param = iter.next();
if (param.startsWith(prefix)) {
sink.put(param.substring(prefix.length() + 1), params.get(param));
}
}
return sink;
}
public final CollectionOp fun;
final CollectionAction action;
final 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);
}
}
interface CollectionOp {
Map<String, Object> execute(SolrQueryRequest req, SolrQueryResponse rsp, CollectionsHandler h)
throws Exception;
}
@Override
public Boolean registerV2() {
return Boolean.TRUE;
}
@Override
public Collection<Class<? extends JerseyResource>> getJerseyResources() {
return List.of(
AddReplicaPropertyAPI.class,
CreateAliasAPI.class,
CreateCollectionAPI.class,
CreateCollectionBackupAPI.class,
CreateShardAPI.class,
DeleteAliasAPI.class,
DeleteCollectionBackupAPI.class,
DeleteCollectionAPI.class,
DeleteReplicaAPI.class,
DeleteReplicaPropertyAPI.class,
DeleteShardAPI.class,
InstallShardDataAPI.class,
ListCollectionsAPI.class,
ListCollectionBackupsAPI.class,
ReplaceNodeAPI.class,
RestoreCollectionAPI.class,
CollectionPropertyAPI.class,
DeleteNodeAPI.class,
ListAliasesAPI.class,
AliasPropertyAPI.class,
ListCollectionSnapshotsAPI.class,
CreateCollectionSnapshotAPI.class,
DeleteCollectionSnapshotAPI.class);
}
@Override
public Collection<Api> getApis() {
final List<Api> apis = new ArrayList<>();
apis.addAll(AnnotatedApi.getApis(new SplitShardAPI(this)));
apis.addAll(AnnotatedApi.getApis(new AddReplicaAPI(this)));
apis.addAll(AnnotatedApi.getApis(new SyncShardAPI(this)));
apis.addAll(AnnotatedApi.getApis(new ForceLeaderAPI(this)));
apis.addAll(AnnotatedApi.getApis(new BalanceShardUniqueAPI(this)));
apis.addAll(AnnotatedApi.getApis(new MigrateDocsAPI(this)));
apis.addAll(AnnotatedApi.getApis(new ModifyCollectionAPI(this)));
apis.addAll(AnnotatedApi.getApis(new MoveReplicaAPI(this)));
apis.addAll(AnnotatedApi.getApis(new RebalanceLeadersAPI(this)));
apis.addAll(AnnotatedApi.getApis(new ReloadCollectionAPI(this)));
apis.addAll(AnnotatedApi.getApis(new CollectionStatusAPI(this)));
apis.addAll(AnnotatedApi.getApis(new RenameCollectionAPI(this)));
return apis;
}
// 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));
}
}