blob: 6d65708b99b08adb73d8b0f762d6ad0c8e4e1402 [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.common.cloud.DocCollection.DOC_ROUTER;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.IOUtils;
import org.apache.solr.cloud.CloudDescriptor;
import org.apache.solr.cloud.Overseer;
import org.apache.solr.cloud.SyncStrategy;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.DocRouter;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.params.CoreAdminParams.CoreAdminAction;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.params.UpdateParams;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.CoreDescriptor;
import org.apache.solr.core.DirectoryFactory;
import org.apache.solr.core.DirectoryFactory.DirContext;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrXMLCoresLocator;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.update.CommitUpdateCommand;
import org.apache.solr.update.MergeIndexesCommand;
import org.apache.solr.update.SplitIndexCommand;
import org.apache.solr.update.UpdateLog;
import org.apache.solr.update.processor.UpdateRequestProcessor;
import org.apache.solr.update.processor.UpdateRequestProcessorChain;
import org.apache.solr.util.DefaultSolrThreadFactory;
import org.apache.solr.util.NumberUtils;
import org.apache.solr.util.RefCounted;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
/**
*
* @since solr 1.3
*/
public class CoreAdminHandler extends RequestHandlerBase {
protected static Logger log = LoggerFactory.getLogger(CoreAdminHandler.class);
protected final CoreContainer coreContainer;
protected static HashMap<String, Map<String, TaskObject>> requestStatusMap =
new HashMap<String,Map<String, TaskObject>>();
protected final ExecutorService parallelExecutor = Executors.newFixedThreadPool(50,
new DefaultSolrThreadFactory("parallelCoreAdminExecutor"));
protected static int MAX_TRACKED_REQUESTS = 100;
public static String RUNNING = "running";
public static String COMPLETED = "completed";
public static String FAILED = "failed";
public static String RESPONSE = "Response";
public static String RESPONSE_STATUS = "STATUS";
public static String RESPONSE_MESSAGE = "msg";
static {
requestStatusMap.put(RUNNING, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
requestStatusMap.put(COMPLETED, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
requestStatusMap.put(FAILED, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
}
public CoreAdminHandler() {
super();
// Unlike most request handlers, CoreContainer initialization
// should happen in the constructor...
this.coreContainer = null;
}
/**
* Overloaded ctor to inject CoreContainer into the handler.
*
* @param coreContainer Core Container of the solr webapp installed.
*/
public CoreAdminHandler(final CoreContainer coreContainer) {
this.coreContainer = coreContainer;
}
@Override
final public void init(NamedList args) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"CoreAdminHandler should not be configured in solrconf.xml\n" +
"it is a special Handler configured directly by the RequestDispatcher");
}
/**
* 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 = getCoreContainer();
if (cores == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Core container instance missing");
}
//boolean doPersist = false;
String taskId = req.getParams().get("async");
TaskObject taskObject = new TaskObject(taskId);
if(taskId != null) {
// Put the tasks into the maps for tracking
if (getMap(RUNNING).containsKey(taskId) || getMap(COMPLETED).containsKey(taskId) || getMap(FAILED).containsKey(taskId)) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Duplicate request with the same requestid found.");
}
addTask(RUNNING, taskObject);
}
// Pick the action
SolrParams params = req.getParams();
CoreAdminAction action = CoreAdminAction.STATUS;
String a = params.get(CoreAdminParams.ACTION);
if (a != null) {
action = CoreAdminAction.get(a);
if (action == null) {
this.handleCustomAction(req, rsp);
}
}
if (taskId == null) {
handleRequestInternal(req, rsp, action);
} else {
ParallelCoreAdminHandlerThread parallelHandlerThread = new ParallelCoreAdminHandlerThread(req, rsp, action, taskObject);
parallelExecutor.execute(parallelHandlerThread);
}
}
protected void handleRequestInternal(SolrQueryRequest req, SolrQueryResponse rsp, CoreAdminAction action) throws Exception {
if (action != null) {
switch (action) {
case CREATE: {
this.handleCreateAction(req, rsp);
break;
}
case RENAME: {
this.handleRenameAction(req, rsp);
break;
}
case UNLOAD: {
this.handleUnloadAction(req, rsp);
break;
}
case STATUS: {
this.handleStatusAction(req, rsp);
break;
}
case PERSIST: {
this.handlePersistAction(req, rsp);
break;
}
case RELOAD: {
this.handleReloadAction(req, rsp);
break;
}
case SWAP: {
this.handleSwapAction(req, rsp);
break;
}
case MERGEINDEXES: {
this.handleMergeAction(req, rsp);
break;
}
case SPLIT: {
this.handleSplitAction(req, rsp);
break;
}
case PREPRECOVERY: {
this.handleWaitForStateAction(req, rsp);
break;
}
case REQUESTRECOVERY: {
this.handleRequestRecoveryAction(req, rsp);
break;
}
case REQUESTSYNCSHARD: {
this.handleRequestSyncAction(req, rsp);
break;
}
// todo : Can this be done by the regular RecoveryStrategy route?
case REQUESTAPPLYUPDATES: {
this.handleRequestApplyUpdatesAction(req, rsp);
break;
}
case REQUESTBUFFERUPDATES: {
this.handleRequestBufferUpdatesAction(req, rsp);
break;
}
case REQUESTSTATUS: {
this.handleRequestActionStatus(req, rsp);
break;
}
case OVERSEEROP:{
ZkController zkController = coreContainer.getZkController();
if(zkController != null){
String op = req.getParams().get("op");
String electionNode = req.getParams().get("electionNode");
if(electionNode != null) {
zkController.rejoinOverseerElection(electionNode, "rejoinAtHead".equals(op));
} else {
log.info("electionNode is required param");
}
}
break;
}
default: {
this.handleCustomAction(req, rsp);
break;
}
case LOAD:
break;
}
}
rsp.setHttpCaching(false);
}
/**
* Handle the core admin SPLIT action.
*/
protected void handleSplitAction(SolrQueryRequest adminReq, SolrQueryResponse rsp) throws IOException {
SolrParams params = adminReq.getParams();
List<DocRouter.Range> ranges = null;
String[] pathsArr = params.getParams("path");
String rangesStr = params.get(CoreAdminParams.RANGES); // ranges=a-b,c-d,e-f
if (rangesStr != null) {
String[] rangesArr = rangesStr.split(",");
if (rangesArr.length == 0) {
throw new SolrException(ErrorCode.BAD_REQUEST, "There must be at least one range specified to split an index");
} else {
ranges = new ArrayList<>(rangesArr.length);
for (String r : rangesArr) {
try {
ranges.add(DocRouter.DEFAULT.fromString(r));
} catch (Exception e) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Exception parsing hexadecimal hash range: " + r, e);
}
}
}
}
String splitKey = params.get("split.key");
String[] newCoreNames = params.getParams("targetCore");
String cname = params.get(CoreAdminParams.CORE, "");
if ((pathsArr == null || pathsArr.length == 0) && (newCoreNames == null || newCoreNames.length == 0)) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Either path or targetCore param must be specified");
}
log.info("Invoked split action for core: " + cname);
SolrCore core = coreContainer.getCore(cname);
SolrQueryRequest req = new LocalSolrQueryRequest(core, params);
List<SolrCore> newCores = null;
try {
// TODO: allow use of rangesStr in the future
List<String> paths = null;
int partitions = pathsArr != null ? pathsArr.length : newCoreNames.length;
DocRouter router = null;
String routeFieldName = null;
if (coreContainer.isZooKeeperAware()) {
ClusterState clusterState = coreContainer.getZkController().getClusterState();
String collectionName = req.getCore().getCoreDescriptor().getCloudDescriptor().getCollectionName();
DocCollection collection = clusterState.getCollection(collectionName);
String sliceName = req.getCore().getCoreDescriptor().getCloudDescriptor().getShardId();
Slice slice = clusterState.getSlice(collectionName, sliceName);
router = collection.getRouter() != null ? collection.getRouter() : DocRouter.DEFAULT;
if (ranges == null) {
DocRouter.Range currentRange = slice.getRange();
ranges = currentRange != null ? router.partitionRange(partitions, currentRange) : null;
}
Object routerObj = collection.get(DOC_ROUTER); // for back-compat with Solr 4.4
if (routerObj != null && routerObj instanceof Map) {
Map routerProps = (Map) routerObj;
routeFieldName = (String) routerProps.get("field");
}
}
if (pathsArr == null) {
newCores = new ArrayList<>(partitions);
for (String newCoreName : newCoreNames) {
SolrCore newcore = coreContainer.getCore(newCoreName);
if (newcore != null) {
newCores.add(newcore);
} else {
throw new SolrException(ErrorCode.BAD_REQUEST, "Core with core name " + newCoreName + " expected but doesn't exist.");
}
}
} else {
paths = Arrays.asList(pathsArr);
}
SplitIndexCommand cmd = new SplitIndexCommand(req, paths, newCores, ranges, router, routeFieldName, splitKey);
core.getUpdateHandler().split(cmd);
// After the split has completed, someone (here?) should start the process of replaying the buffered updates.
} catch (Exception e) {
log.error("ERROR executing split:", e);
throw new RuntimeException(e);
} finally {
if (req != null) req.close();
if (core != null) core.close();
if (newCores != null) {
for (SolrCore newCore : newCores) {
newCore.close();
}
}
}
}
protected void handleMergeAction(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
SolrParams params = req.getParams();
String cname = params.required().get(CoreAdminParams.CORE);
SolrCore core = coreContainer.getCore(cname);
SolrQueryRequest wrappedReq = null;
List<SolrCore> sourceCores = Lists.newArrayList();
List<RefCounted<SolrIndexSearcher>> searchers = Lists.newArrayList();
// stores readers created from indexDir param values
List<DirectoryReader> readersToBeClosed = Lists.newArrayList();
List<Directory> dirsToBeReleased = Lists.newArrayList();
if (core != null) {
try {
String[] dirNames = params.getParams(CoreAdminParams.INDEX_DIR);
if (dirNames == null || dirNames.length == 0) {
String[] sources = params.getParams("srcCore");
if (sources == null || sources.length == 0)
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST,
"At least one indexDir or srcCore must be specified");
for (int i = 0; i < sources.length; i++) {
String source = sources[i];
SolrCore srcCore = coreContainer.getCore(source);
if (srcCore == null)
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Core: " + source + " does not exist");
sourceCores.add(srcCore);
}
} else {
DirectoryFactory dirFactory = core.getDirectoryFactory();
for (int i = 0; i < dirNames.length; i++) {
Directory dir = dirFactory.get(dirNames[i], DirContext.DEFAULT, core.getSolrConfig().indexConfig.lockType);
dirsToBeReleased.add(dir);
// TODO: why doesn't this use the IR factory? what is going on here?
readersToBeClosed.add(DirectoryReader.open(dir));
}
}
List<DirectoryReader> readers = null;
if (readersToBeClosed.size() > 0) {
readers = readersToBeClosed;
} else {
readers = Lists.newArrayList();
for (SolrCore solrCore: sourceCores) {
// record the searchers so that we can decref
RefCounted<SolrIndexSearcher> searcher = solrCore.getSearcher();
searchers.add(searcher);
readers.add(searcher.get().getIndexReader());
}
}
UpdateRequestProcessorChain processorChain =
core.getUpdateProcessingChain(params.get(UpdateParams.UPDATE_CHAIN));
wrappedReq = new LocalSolrQueryRequest(core, req.getParams());
UpdateRequestProcessor processor =
processorChain.createProcessor(wrappedReq, rsp);
processor.processMergeIndexes(new MergeIndexesCommand(readers, req));
} catch (Exception e) {
// log and rethrow so that if the finally fails we don't lose the original problem
log.error("ERROR executing merge:", e);
throw e;
} finally {
for (RefCounted<SolrIndexSearcher> searcher : searchers) {
if (searcher != null) searcher.decref();
}
for (SolrCore solrCore : sourceCores) {
if (solrCore != null) solrCore.close();
}
IOUtils.closeWhileHandlingException(readersToBeClosed);
for (Directory dir : dirsToBeReleased) {
DirectoryFactory dirFactory = core.getDirectoryFactory();
dirFactory.release(dir);
}
if (wrappedReq != null) wrappedReq.close();
core.close();
}
}
}
/**
* Handle Custom Action.
* <p/>
* This method could be overridden by derived classes to handle custom actions. <br> By default - this method throws a
* solr exception. Derived classes are free to write their derivation if necessary.
*/
protected void handleCustomAction(SolrQueryRequest req, SolrQueryResponse rsp) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unsupported operation: " +
req.getParams().get(CoreAdminParams.ACTION));
}
public static ImmutableMap<String, String> paramToProp = ImmutableMap.<String, String>builder()
.put(CoreAdminParams.CONFIG, CoreDescriptor.CORE_CONFIG)
.put(CoreAdminParams.SCHEMA, CoreDescriptor.CORE_SCHEMA)
.put(CoreAdminParams.DATA_DIR, CoreDescriptor.CORE_DATADIR)
.put(CoreAdminParams.ULOG_DIR, CoreDescriptor.CORE_ULOGDIR)
.put(CoreAdminParams.CONFIGSET, CoreDescriptor.CORE_CONFIGSET)
.put(CoreAdminParams.LOAD_ON_STARTUP, CoreDescriptor.CORE_LOADONSTARTUP)
.put(CoreAdminParams.TRANSIENT, CoreDescriptor.CORE_TRANSIENT)
.put(CoreAdminParams.SHARD, CoreDescriptor.CORE_SHARD)
.put(CoreAdminParams.COLLECTION, CoreDescriptor.CORE_COLLECTION)
.put(CoreAdminParams.ROLES, CoreDescriptor.CORE_ROLES)
.put(CoreAdminParams.CORE_NODE_NAME, CoreDescriptor.CORE_NODE_NAME)
.put(ZkStateReader.NUM_SHARDS_PROP, CloudDescriptor.NUM_SHARDS)
.build();
public static ImmutableMap<String, String> cloudParamToProp;
protected static CoreDescriptor buildCoreDescriptor(SolrParams params, CoreContainer container) {
String name = checkNotEmpty(params.get(CoreAdminParams.NAME),
"Missing parameter [" + CoreAdminParams.NAME + "]");
Properties coreProps = new Properties();
for (String param : paramToProp.keySet()) {
String value = params.get(param, null);
if (StringUtils.isNotEmpty(value)) {
coreProps.setProperty(paramToProp.get(param), value);
}
}
Iterator<String> paramsIt = params.getParameterNamesIterator();
while (paramsIt.hasNext()) {
String param = paramsIt.next();
if (!param.startsWith(CoreAdminParams.PROPERTY_PREFIX))
continue;
String propName = param.substring(CoreAdminParams.PROPERTY_PREFIX.length());
String propValue = params.get(param);
coreProps.setProperty(propName, propValue);
}
String instancedir = params.get(CoreAdminParams.INSTANCE_DIR);
if (StringUtils.isEmpty(instancedir) && coreProps.getProperty(CoreAdminParams.INSTANCE_DIR) != null) {
instancedir = coreProps.getProperty(CoreAdminParams.INSTANCE_DIR);
} else if (StringUtils.isEmpty(instancedir)){
instancedir = name; // will be resolved later against solr.home
//instancedir = container.getSolrHome() + "/" + name;
}
return new CoreDescriptor(container, name, instancedir, coreProps, params);
}
private static String checkNotEmpty(String value, String message) {
if (StringUtils.isEmpty(value))
throw new SolrException(ErrorCode.BAD_REQUEST, message);
return value;
}
/**
* Handle 'CREATE' action.
*
* @throws SolrException in case of a configuration error.
*/
protected void handleCreateAction(SolrQueryRequest req, SolrQueryResponse rsp) throws SolrException {
SolrParams params = req.getParams();
log.info("core create command {}", params);
CoreDescriptor dcore = buildCoreDescriptor(params, coreContainer);
if (coreContainer.getAllCoreNames().contains(dcore.getName())) {
log.warn("Creating a core with existing name is not allowed");
throw new SolrException(ErrorCode.SERVER_ERROR,
"Core with name '" + dcore.getName() + "' already exists.");
}
// TODO this should be moved into CoreContainer, really...
boolean preExisitingZkEntry = false;
try {
if (coreContainer.getZkController() != null) {
if (!Overseer.isLegacy(coreContainer.getZkController().getZkStateReader().getClusterProps())) {
if (dcore.getCloudDescriptor().getCoreNodeName() == null) {
throw new SolrException(ErrorCode.SERVER_ERROR,
"non legacy mode coreNodeName missing " + params);
}
}
preExisitingZkEntry = checkIfCoreNodeNameAlreadyExists(dcore);
}
SolrCore core = coreContainer.create(dcore);
// only write out the descriptor if the core is successfully created
coreContainer.getCoresLocator().create(coreContainer, dcore);
if (coreContainer.getCoresLocator() instanceof SolrXMLCoresLocator) {
// hack - in this case we persist once more because a core create race might
// have dropped entries.
coreContainer.getCoresLocator().create(coreContainer);
}
rsp.add("core", core.getName());
}
catch (Exception ex) {
if (coreContainer.isZooKeeperAware() && dcore != null && !preExisitingZkEntry) {
try {
coreContainer.getZkController().unregister(dcore.getName(), dcore);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
SolrException.log(log, null, e);
} catch (KeeperException e) {
SolrException.log(log, null, e);
}
}
Throwable tc = ex;
Throwable c = null;
do {
tc = tc.getCause();
if (tc != null) {
c = tc;
}
} while (tc != null);
String rootMsg = "";
if (c != null) {
rootMsg = " Caused by: " + c.getMessage();
}
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Error CREATEing SolrCore '" + dcore.getName() + "': " +
ex.getMessage() + rootMsg, ex);
}
}
private boolean checkIfCoreNodeNameAlreadyExists(CoreDescriptor dcore) {
ZkStateReader zkStateReader = coreContainer.getZkController()
.getZkStateReader();
DocCollection collection = zkStateReader.getClusterState().getCollectionOrNull(dcore.getCollectionName());
if (collection != null) {
Collection<Slice> slices = collection.getSlices();
for (Slice slice : slices) {
Collection<Replica> replicas = slice.getReplicas();
for (Replica replica : replicas) {
if (replica.getName().equals(
dcore.getCloudDescriptor().getCoreNodeName())) {
return true;
}
}
}
}
return false;
}
/**
* Handle "RENAME" Action
*/
protected void handleRenameAction(SolrQueryRequest req, SolrQueryResponse rsp) throws SolrException {
SolrParams params = req.getParams();
String name = params.get(CoreAdminParams.OTHER);
String cname = params.get(CoreAdminParams.CORE);
if (cname.equals(name)) return;
coreContainer.rename(cname, name);
}
/**
* Handle "ALIAS" action
*/
@Deprecated
protected void handleAliasAction(SolrQueryRequest req, SolrQueryResponse rsp) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "The ALIAS action is no longer supported");
}
/**
* Handle "UNLOAD" Action
*/
protected void handleUnloadAction(SolrQueryRequest req, SolrQueryResponse rsp) throws SolrException {
SolrParams params = req.getParams();
String cname = params.get(CoreAdminParams.CORE);
boolean deleteIndexDir = params.getBool(CoreAdminParams.DELETE_INDEX, false);
boolean deleteDataDir = params.getBool(CoreAdminParams.DELETE_DATA_DIR, false);
boolean deleteInstanceDir = params.getBool(CoreAdminParams.DELETE_INSTANCE_DIR, false);
coreContainer.unload(cname, deleteIndexDir, deleteDataDir, deleteInstanceDir);
}
/**
* Handle "STATUS" action
*/
protected void handleStatusAction(SolrQueryRequest req, SolrQueryResponse rsp)
throws SolrException {
SolrParams params = req.getParams();
String cname = params.get(CoreAdminParams.CORE);
String indexInfo = params.get(CoreAdminParams.INDEX_INFO);
boolean isIndexInfoNeeded = Boolean.parseBoolean(null == indexInfo ? "true" : indexInfo);
NamedList<Object> status = new SimpleOrderedMap<>();
Map<String, Exception> failures = new HashMap<>();
for (Map.Entry<String, CoreContainer.CoreLoadFailure> failure : coreContainer.getCoreInitFailures().entrySet()) {
failures.put(failure.getKey(), failure.getValue().exception);
}
try {
if (cname == null) {
rsp.add("defaultCoreName", coreContainer.getDefaultCoreName());
for (String name : coreContainer.getAllCoreNames()) {
status.add(name, getCoreStatus(coreContainer, name, isIndexInfoNeeded));
}
rsp.add("initFailures", failures);
} else {
failures = failures.containsKey(cname)
? Collections.singletonMap(cname, failures.get(cname))
: Collections.<String, Exception>emptyMap();
rsp.add("initFailures", failures);
status.add(cname, getCoreStatus(coreContainer, cname, isIndexInfoNeeded));
}
rsp.add("status", status);
} catch (Exception ex) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Error handling 'status' action ", ex);
}
}
/**
* Handler "PERSIST" action
*/
protected void handlePersistAction(SolrQueryRequest req, SolrQueryResponse rsp)
throws SolrException {
rsp.add("message", "The PERSIST action has been deprecated");
}
/**
* Handler "RELOAD" action
*/
protected void handleReloadAction(SolrQueryRequest req, SolrQueryResponse rsp) {
SolrParams params = req.getParams();
String cname = params.get(CoreAdminParams.CORE);
if(!coreContainer.getCoreNames().contains(cname)) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Core with core name [" + cname + "] does not exist.");
}
try {
coreContainer.reload(cname);
} catch (Exception ex) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error handling 'reload' action", ex);
}
}
/**
* Handle "REQUESTSTATUS" action
*/
protected void handleRequestActionStatus(SolrQueryRequest req, SolrQueryResponse rsp) {
SolrParams params = req.getParams();
String requestId = params.get(CoreAdminParams.REQUESTID);
log.info("Checking request status for : " + requestId);
if (mapContainsTask(RUNNING, requestId)) {
rsp.add(RESPONSE_STATUS, RUNNING);
} else if(mapContainsTask(COMPLETED, requestId)) {
rsp.add(RESPONSE_STATUS, COMPLETED);
rsp.add(RESPONSE, getMap(COMPLETED).get(requestId).getRspObject());
} else if(mapContainsTask(FAILED, requestId)) {
rsp.add(RESPONSE_STATUS, FAILED);
rsp.add(RESPONSE, getMap(FAILED).get(requestId).getRspObject());
} else {
rsp.add(RESPONSE_STATUS, "notfound");
rsp.add(RESPONSE_MESSAGE, "No task found in running, completed or failed tasks");
}
}
/**
* Handle "SWAP" action
*/
protected void handleSwapAction(SolrQueryRequest req, SolrQueryResponse rsp) {
final SolrParams params = req.getParams();
final SolrParams required = params.required();
final String cname = params.get(CoreAdminParams.CORE);
String other = required.get(CoreAdminParams.OTHER);
coreContainer.swap(cname, other);
}
protected void handleRequestRecoveryAction(SolrQueryRequest req,
SolrQueryResponse rsp) throws IOException {
final SolrParams params = req.getParams();
log.info("It has been requested that we recover: core="+params.get(CoreAdminParams.CORE));
Thread thread = new Thread() {
@Override
public void run() {
String cname = params.get(CoreAdminParams.CORE);
if (cname == null) {
cname = "";
}
try (SolrCore core = coreContainer.getCore(cname)) {
if (core != null) {
// try to publish as recovering right away
try {
coreContainer.getZkController().publish(core.getCoreDescriptor(), ZkStateReader.RECOVERING);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
SolrException.log(log, "", e);
} catch (Throwable e) {
SolrException.log(log, "", e);
if (e instanceof Error) {
throw (Error) e;
}
}
core.getUpdateHandler().getSolrCoreState().doRecovery(coreContainer, core.getCoreDescriptor());
} else {
SolrException.log(log, "Could not find core to call recovery:" + cname);
}
}
}
};
thread.start();
}
protected void handleRequestSyncAction(SolrQueryRequest req,
SolrQueryResponse rsp) throws IOException {
final SolrParams params = req.getParams();
log.info("I have been requested to sync up my shard");
ZkController zkController = coreContainer.getZkController();
if (zkController == null) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Only valid for SolrCloud");
}
String cname = params.get(CoreAdminParams.CORE);
if (cname == null) {
throw new IllegalArgumentException(CoreAdminParams.CORE + " is required");
}
SyncStrategy syncStrategy = null;
try (SolrCore core = coreContainer.getCore(cname)) {
if (core != null) {
syncStrategy = new SyncStrategy(core.getCoreDescriptor().getCoreContainer());
Map<String,Object> props = new HashMap<>();
props.put(ZkStateReader.BASE_URL_PROP, zkController.getBaseUrl());
props.put(ZkStateReader.CORE_NAME_PROP, cname);
props.put(ZkStateReader.NODE_NAME_PROP, zkController.getNodeName());
boolean success = syncStrategy.sync(zkController, core, new ZkNodeProps(props), true);
// solrcloud_debug
if (log.isDebugEnabled()) {
try {
RefCounted<SolrIndexSearcher> searchHolder = core
.getNewestSearcher(false);
SolrIndexSearcher searcher = searchHolder.get();
try {
log.debug(core.getCoreDescriptor().getCoreContainer()
.getZkController().getNodeName()
+ " synched "
+ searcher.search(new MatchAllDocsQuery(), 1).totalHits);
} finally {
searchHolder.decref();
}
} catch (Exception e) {
throw new SolrException(ErrorCode.SERVER_ERROR, null, e);
}
}
if (!success) {
throw new SolrException(ErrorCode.SERVER_ERROR, "Sync Failed");
}
} else {
SolrException.log(log, "Cound not find core to call sync:" + cname);
}
} finally {
// no recoveryStrat close for now
if (syncStrategy != null) {
syncStrategy.close();
}
}
}
protected void handleWaitForStateAction(SolrQueryRequest req,
SolrQueryResponse rsp) throws IOException, InterruptedException, KeeperException {
final SolrParams params = req.getParams();
String cname = params.get(CoreAdminParams.CORE);
if (cname == null) {
cname = "";
}
String nodeName = params.get("nodeName");
String coreNodeName = params.get("coreNodeName");
String waitForState = params.get("state");
Boolean checkLive = params.getBool("checkLive");
Boolean onlyIfLeader = params.getBool("onlyIfLeader");
Boolean onlyIfLeaderActive = params.getBool("onlyIfLeaderActive");
log.info("Going to wait for coreNodeName: " + coreNodeName + ", state: " + waitForState
+ ", checkLive: " + checkLive + ", onlyIfLeader: " + onlyIfLeader
+ ", onlyIfLeaderActive: "+onlyIfLeaderActive);
int maxTries = 0;
String state = null;
boolean live = false;
int retry = 0;
while (true) {
try (SolrCore core = coreContainer.getCore(cname)) {
if (core == null && retry == 30) {
throw new SolrException(ErrorCode.BAD_REQUEST, "core not found:"
+ cname);
}
if (core != null) {
if (onlyIfLeader != null && onlyIfLeader) {
if (!core.getCoreDescriptor().getCloudDescriptor().isLeader()) {
throw new SolrException(ErrorCode.BAD_REQUEST, "We are not the leader");
}
}
// wait until we are sure the recovering node is ready
// to accept updates
CloudDescriptor cloudDescriptor = core.getCoreDescriptor()
.getCloudDescriptor();
if (retry % 15 == 0) {
if (retry > 0 && log.isInfoEnabled())
log.info("After " + retry + " seconds, core " + cname + " (" +
cloudDescriptor.getShardId() + " of " +
cloudDescriptor.getCollectionName() + ") still does not have state: " +
waitForState + "; forcing ClusterState update from ZooKeeper");
// force a cluster state update
coreContainer.getZkController().getZkStateReader().updateClusterState(true);
}
if (maxTries == 0) {
// wait long enough for the leader conflict to work itself out plus a little extra
int conflictWaitMs = coreContainer.getZkController().getLeaderConflictResolveWait();
maxTries = (int) Math.round(conflictWaitMs / 1000) + 3;
log.info("Will wait a max of " + maxTries + " seconds to see " + cname + " (" +
cloudDescriptor.getShardId() + " of " +
cloudDescriptor.getCollectionName() + ") have state: " + waitForState);
}
ClusterState clusterState = coreContainer.getZkController()
.getClusterState();
String collection = cloudDescriptor.getCollectionName();
Slice slice = clusterState.getSlice(collection,
cloudDescriptor.getShardId());
if (slice != null) {
ZkNodeProps nodeProps = slice.getReplicasMap().get(coreNodeName);
if (nodeProps != null) {
state = nodeProps.getStr(ZkStateReader.STATE_PROP);
live = clusterState.liveNodesContain(nodeName);
String localState = cloudDescriptor.getLastPublished();
// TODO: This is funky but I've seen this in testing where the replica asks the
// leader to be in recovery? Need to track down how that happens ... in the meantime,
// this is a safeguard
boolean leaderDoesNotNeedRecovery = (onlyIfLeader != null &&
onlyIfLeader &&
core.getName().equals(nodeProps.getStr("core")) &&
ZkStateReader.RECOVERING.equals(waitForState) &&
ZkStateReader.ACTIVE.equals(localState) &&
ZkStateReader.ACTIVE.equals(state));
if (leaderDoesNotNeedRecovery) {
log.warn("Leader "+core.getName()+" ignoring request to be in the recovering state because it is live and active.");
}
boolean onlyIfActiveCheckResult = onlyIfLeaderActive != null && onlyIfLeaderActive && (localState == null || !localState.equals(ZkStateReader.ACTIVE));
log.info("In WaitForState("+waitForState+"): collection="+collection+", shard="+slice.getName()+
", thisCore="+core.getName()+", leaderDoesNotNeedRecovery="+leaderDoesNotNeedRecovery+
", isLeader? "+core.getCoreDescriptor().getCloudDescriptor().isLeader()+
", live="+live+", checkLive="+checkLive+", currentState="+state+", localState="+localState+", nodeName="+nodeName+
", coreNodeName="+coreNodeName+", onlyIfActiveCheckResult="+onlyIfActiveCheckResult+", nodeProps: "+nodeProps);
if (!onlyIfActiveCheckResult && nodeProps != null && (state.equals(waitForState) || leaderDoesNotNeedRecovery)) {
if (checkLive == null) {
break;
} else if (checkLive && live) {
break;
} else if (!checkLive && !live) {
break;
}
}
}
}
}
if (retry++ == maxTries) {
String collection = null;
String leaderInfo = null;
String shardId = null;
try {
CloudDescriptor cloudDescriptor =
core.getCoreDescriptor().getCloudDescriptor();
collection = cloudDescriptor.getCollectionName();
shardId = cloudDescriptor.getShardId();
leaderInfo = coreContainer.getZkController().
getZkStateReader().getLeaderUrl(collection, shardId, 5000);
} catch (Exception exc) {
leaderInfo = "Not available due to: " + exc;
}
throw new SolrException(ErrorCode.BAD_REQUEST,
"I was asked to wait on state " + waitForState + " for "
+ shardId + " in " + collection + " on " + nodeName
+ " but I still do not see the requested state. I see state: "
+ state + " live:" + live + " leader from ZK: " + leaderInfo
);
}
if (coreContainer.isShutDown()) {
throw new SolrException(ErrorCode.BAD_REQUEST,
"Solr is shutting down");
}
// solrcloud_debug
if (log.isDebugEnabled()) {
try {
LocalSolrQueryRequest r = new LocalSolrQueryRequest(core,
new ModifiableSolrParams());
CommitUpdateCommand commitCmd = new CommitUpdateCommand(r, false);
commitCmd.softCommit = true;
core.getUpdateHandler().commit(commitCmd);
RefCounted<SolrIndexSearcher> searchHolder = core
.getNewestSearcher(false);
SolrIndexSearcher searcher = searchHolder.get();
try {
log.debug(core.getCoreDescriptor().getCoreContainer()
.getZkController().getNodeName()
+ " to replicate "
+ searcher.search(new MatchAllDocsQuery(), 1).totalHits
+ " gen:"
+ core.getDeletionPolicy().getLatestCommit().getGeneration()
+ " data:" + core.getDataDir());
} finally {
searchHolder.decref();
}
} catch (Exception e) {
throw new SolrException(ErrorCode.SERVER_ERROR, null, e);
}
}
}
Thread.sleep(1000);
}
log.info("Waited coreNodeName: " + coreNodeName + ", state: " + waitForState
+ ", checkLive: " + checkLive + ", onlyIfLeader: " + onlyIfLeader + " for: " + retry + " seconds.");
}
private void handleRequestApplyUpdatesAction(SolrQueryRequest req, SolrQueryResponse rsp) {
SolrParams params = req.getParams();
String cname = params.get(CoreAdminParams.NAME, "");
log.info("Applying buffered updates on core: " + cname);
try (SolrCore core = coreContainer.getCore(cname)) {
if (core == null)
throw new SolrException(ErrorCode.BAD_REQUEST, "Core [" + cname + "] not found");
UpdateLog updateLog = core.getUpdateHandler().getUpdateLog();
if (updateLog.getState() != UpdateLog.State.BUFFERING) {
throw new SolrException(ErrorCode.SERVER_ERROR, "Core " + cname + " not in buffering state");
}
Future<UpdateLog.RecoveryInfo> future = updateLog.applyBufferedUpdates();
if (future == null) {
log.info("No buffered updates available. core=" + cname);
rsp.add("core", cname);
rsp.add("status", "EMPTY_BUFFER");
return;
}
UpdateLog.RecoveryInfo report = future.get();
if (report.failed) {
SolrException.log(log, "Replay failed");
throw new SolrException(ErrorCode.SERVER_ERROR, "Replay failed");
}
coreContainer.getZkController().publish(core.getCoreDescriptor(), ZkStateReader.ACTIVE);
rsp.add("core", cname);
rsp.add("status", "BUFFER_APPLIED");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("Recovery was interrupted", e);
} catch (Exception e) {
if (e instanceof SolrException)
throw (SolrException)e;
else
throw new SolrException(ErrorCode.SERVER_ERROR, "Could not apply buffered updates", e);
} finally {
if (req != null) req.close();
}
}
private void handleRequestBufferUpdatesAction(SolrQueryRequest req, SolrQueryResponse rsp) {
SolrParams params = req.getParams();
String cname = params.get(CoreAdminParams.NAME, "");
log.info("Starting to buffer updates on core:" + cname);
try (SolrCore core = coreContainer.getCore(cname)) {
if (core == null)
throw new SolrException(ErrorCode.BAD_REQUEST, "Core [" + cname + "] does not exist");
UpdateLog updateLog = core.getUpdateHandler().getUpdateLog();
if (updateLog.getState() != UpdateLog.State.ACTIVE) {
throw new SolrException(ErrorCode.SERVER_ERROR, "Core " + cname + " not in active state");
}
updateLog.bufferUpdates();
rsp.add("core", cname);
rsp.add("status", "BUFFERING");
} catch (Throwable e) {
if (e instanceof SolrException)
throw (SolrException)e;
else
throw new SolrException(ErrorCode.SERVER_ERROR, "Could not start buffering updates", e);
} finally {
if (req != null) req.close();
}
}
/**
* Returns the core status for a particular core.
* @param cores - the enclosing core container
* @param cname - the core to return
* @param isIndexInfoNeeded - add what may be expensive index information. NOT returned if the core is not loaded
* @return - a named list of key/value pairs from the core.
* @throws IOException - LukeRequestHandler can throw an I/O exception
*/
protected NamedList<Object> getCoreStatus(CoreContainer cores, String cname, boolean isIndexInfoNeeded) throws IOException {
NamedList<Object> info = new SimpleOrderedMap<>();
if (!cores.isLoaded(cname)) { // Lazily-loaded core, fill in what we can.
// It would be a real mistake to load the cores just to get the status
CoreDescriptor desc = cores.getUnloadedCoreDescriptor(cname);
if (desc != null) {
info.add("name", desc.getName());
info.add("isDefaultCore", desc.getName().equals(cores.getDefaultCoreName()));
info.add("instanceDir", desc.getInstanceDir());
// None of the following are guaranteed to be present in a not-yet-loaded core.
String tmp = desc.getDataDir();
if (StringUtils.isNotBlank(tmp)) info.add("dataDir", tmp);
tmp = desc.getConfigName();
if (StringUtils.isNotBlank(tmp)) info.add("config", tmp);
tmp = desc.getSchemaName();
if (StringUtils.isNotBlank(tmp)) info.add("schema", tmp);
info.add("isLoaded", "false");
}
} else {
try (SolrCore core = cores.getCore(cname)) {
if (core != null) {
info.add("name", core.getName());
info.add("isDefaultCore", core.getName().equals(cores.getDefaultCoreName()));
info.add("instanceDir", normalizePath(core.getResourceLoader().getInstanceDir()));
info.add("dataDir", normalizePath(core.getDataDir()));
info.add("config", core.getConfigResource());
info.add("schema", core.getSchemaResource());
info.add("startTime", new Date(core.getStartTime()));
info.add("uptime", System.currentTimeMillis() - core.getStartTime());
if (isIndexInfoNeeded) {
RefCounted<SolrIndexSearcher> searcher = core.getSearcher();
try {
SimpleOrderedMap<Object> indexInfo = LukeRequestHandler.getIndexInfo(searcher.get().getIndexReader());
long size = getIndexSize(core);
indexInfo.add("sizeInBytes", size);
indexInfo.add("size", NumberUtils.readableSize(size));
info.add("index", indexInfo);
} finally {
searcher.decref();
}
}
}
}
}
return info;
}
private long getIndexSize(SolrCore core) {
Directory dir;
long size = 0;
try {
dir = core.getDirectoryFactory().get(core.getIndexDir(), DirContext.DEFAULT, core.getSolrConfig().indexConfig.lockType);
try {
size = DirectoryFactory.sizeOfDirectory(dir);
} finally {
core.getDirectoryFactory().release(dir);
}
} catch (IOException e) {
SolrException.log(log, "IO error while trying to get the size of the Directory", e);
}
return size;
}
protected static String normalizePath(String path) {
if (path == null)
return null;
path = path.replace('/', File.separatorChar);
path = path.replace('\\', File.separatorChar);
return path;
}
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 Multiple Solr Cores";
}
@Override
public String getSource() {
return null;
}
/**
* Class to implement multi-threaded CoreAdminHandler behaviour.
* This accepts all of the context from handleRequestBody.
*/
protected class ParallelCoreAdminHandlerThread implements Runnable {
SolrQueryRequest req;
SolrQueryResponse rsp;
CoreAdminAction action;
TaskObject taskObject;
public ParallelCoreAdminHandlerThread (SolrQueryRequest req, SolrQueryResponse rsp,
CoreAdminAction action, TaskObject taskObject){
this.req = req;
this.rsp = rsp;
this.action = action;
this.taskObject = taskObject;
}
public void run() {
boolean exceptionCaught = false;
try {
handleRequestInternal(req, rsp, action);
taskObject.setRspObject(rsp);
} catch (Exception e) {
exceptionCaught = true;
taskObject.setRspObjectFromException(e);
} finally {
removeTask("running", taskObject.taskId);
if(exceptionCaught) {
addTask("failed", taskObject, true);
} else
addTask("completed", taskObject, true);
}
}
}
/**
* Helper class to manage the tasks to be tracked.
* This contains the taskId, request and the response (if available).
*/
private class TaskObject {
String taskId;
String rspInfo;
public TaskObject(String taskId) {
this.taskId = taskId;
}
public String getRspObject() {
return rspInfo;
}
public void setRspObject(SolrQueryResponse rspObject) {
this.rspInfo = rspObject.getToLogAsString("TaskId: " + this.taskId + " ");
}
public void setRspObjectFromException(Exception e) {
this.rspInfo = e.getMessage();
}
}
/**
* Helper method to add a task to a tracking map.
*/
protected void addTask(String map, TaskObject o, boolean limit) {
synchronized (getMap(map)) {
if(limit && getMap(map).size() == MAX_TRACKED_REQUESTS) {
String key = getMap(map).entrySet().iterator().next().getKey();
getMap(map).remove(key);
}
addTask(map, o);
}
}
protected void addTask(String map, TaskObject o) {
synchronized (getMap(map)) {
getMap(map).put(o.taskId, o);
}
}
/**
* Helper method to remove a task from a tracking map.
*/
protected void removeTask(String map, String taskId) {
synchronized (getMap(map)) {
getMap(map).remove(taskId);
}
}
/**
* Helper method to check if a map contains a taskObject with the given taskId.
*/
protected boolean mapContainsTask(String map, String taskId) {
return getMap(map).containsKey(taskId);
}
/**
* Helper method to get a TaskObject given a map and a taskId.
*/
protected TaskObject getTask(String map, String taskId) {
return getMap(map).get(taskId);
}
/**
* Helper method to get a request status map given the name.
*/
private Map<String, TaskObject> getMap(String map) {
return requestStatusMap.get(map);
}
/**
* Method to ensure shutting down of the ThreadPool Executor.
*/
public void shutdown() {
if (parallelExecutor != null && !parallelExecutor.isShutdown())
ExecutorUtil.shutdownAndAwaitTermination(parallelExecutor);
}
}