SOLR-6981: add a delete action to the bin/solr script

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1652345 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Timothy Potter 2015-01-16 06:36:15 +00:00
parent 3f0e13d8a4
commit b5df6e6a44
4 changed files with 455 additions and 38 deletions

View File

@ -716,6 +716,9 @@ Other Changes
* SOLR-6982: bin/solr and SolrCLI should support SSL-related Java System Properties
(Timothy Potter)
* SOLR-6981: Add a delete action to the bin/solr script to allow deleting of cores /
collections (with delete collection config directory from ZK) (Timothy Potter)
================== 4.10.3 ==================
Bug Fixes

View File

@ -129,7 +129,7 @@ function print_usage() {
if [ -z "$CMD" ]; then
echo ""
echo "Usage: solr COMMAND OPTIONS"
echo " where COMMAND is one of: start, stop, restart, status, healthcheck, create, create_core, create_collection"
echo " where COMMAND is one of: start, stop, restart, status, healthcheck, create, create_core, create_collection, delete"
echo ""
echo " Standalone server example (start Solr running in the background on port 8984):"
echo ""
@ -229,6 +229,23 @@ function print_usage() {
echo ""
echo " bin/solr create_collection -help"
echo ""
elif [ "$CMD" == "delete" ]; then
echo ""
echo "Usage: solr delete [-c name] [-deleteConfig true|false] [-p port]"
echo ""
echo " Deletes a core or collection depending on whether Solr is running in standalone (core) or SolrCloud"
echo " mode (collection). If you're deleting a collection in SolrCloud mode, the default behavior is to also"
echo " delete the configuration directory from ZooKeeper so long as it is not being used by another collection."
echo " You can override this behavior by passing -deleteConfig false when running this command."
echo ""
echo " -c <name> Name of the core / collection to delete"
echo ""
echo " -deleteConfig <boolean> Delete the configuration directory from ZooKeeper; default is true"
echo ""
echo " -p <port> Port of a local Solr instance where you want to delete the core/collection"
echo " If not specified, the script will search the local system for a running"
echo " Solr instance and will use the port of the first server it finds."
echo ""
elif [ "$CMD" == "create_core" ]; then
echo ""
echo "Usage: solr create_core [-c core] [-d confdir] [-p port]"
@ -645,6 +662,87 @@ if [[ "$SCRIPT_CMD" == "create" || "$SCRIPT_CMD" == "create_core" || "$SCRIPT_CM
fi
fi
# delete a core or collection
if [[ "$SCRIPT_CMD" == "delete" ]]; then
if [ $# -gt 0 ]; then
while true; do
case $1 in
-c|-core|-collection)
if [[ -z "$2" || "${2:0:1}" == "-" ]]; then
print_usage "$SCRIPT_CMD" "name is required when using the $1 option!"
exit 1
fi
DELETE_NAME=$2
shift 2
;;
-p|-port)
if [[ -z "$2" || "${2:0:1}" == "-" ]]; then
print_usage "$SCRIPT_CMD" "Solr port is required when using the $1 option!"
exit 1
fi
DELETE_PORT="$2"
shift 2
;;
-deleteConfig)
if [[ -z "$2" || "${2:0:1}" == "-" ]]; then
print_usage "$SCRIPT_CMD" "true|false is required when using the $1 option!"
exit 1
fi
DELETE_CONFIG="$2"
shift 2
;;
-help|-usage)
print_usage "$SCRIPT_CMD"
exit 0
;;
--)
shift
break
;;
*)
if [ "$1" != "" ]; then
print_usage "$SCRIPT_CMD" "Unrecognized or misplaced argument: $1!"
exit 1
else
break # out-of-args, stop looping
fi
;;
esac
done
fi
if [ -z "$DELETE_NAME" ]; then
echo "Name (-c) argument is required!"
print_usage "$SCRIPT_CMD"
exit 1
fi
# If not defined, use the collection name for the name of the configuration in ZooKeeper
if [ -z "$DELETE_CONFIG" ]; then
DELETE_CONFIG=true
fi
if [ -z "$DELETE_PORT" ]; then
for ID in `ps auxww | grep java | grep start.jar | awk '{print $2}' | sort -r`
do
port=`jetty_port "$ID"`
if [ "$port" != "" ]; then
DELETE_PORT=$port
break
fi
done
fi
if [ -z "$DELETE_PORT" ]; then
echo "Failed to determine the port of a local Solr instance, cannot delete $DELETE_NAME!"
exit 1
fi
run_tool delete -name $DELETE_NAME -deleteConfig $DELETE_CONFIG \
-solrUrl $SOLR_URL_SCHEME://localhost:$DELETE_PORT/solr
exit $?
fi
# verify the command given is supported
if [ "$SCRIPT_CMD" != "stop" ] && [ "$SCRIPT_CMD" != "start" ] && [ "$SCRIPT_CMD" != "restart" ] && [ "$SCRIPT_CMD" != "status" ]; then

View File

@ -96,6 +96,11 @@ IF "%1"=="create_collection" (
SHIFT
goto parse_create_args
)
IF "%1"=="delete" (
set SCRIPT_CMD=delete
SHIFT
goto parse_delete_args
)
goto parse_args
:usage
@ -111,12 +116,13 @@ IF "%SCRIPT_CMD%"=="healthcheck" goto healthcheck_usage
IF "%SCRIPT_CMD%"=="create" goto create_usage
IF "%SCRIPT_CMD%"=="create_core" goto create_core_usage
IF "%SCRIPT_CMD%"=="create_collection" goto create_collection_usage
IF "%SCRIPT_CMD%"=="delete" goto delete_usage
goto done
:script_usage
@echo.
@echo Usage: solr COMMAND OPTIONS
@echo where COMMAND is one of: start, stop, restart, healthcheck, create, create_core, create_collection
@echo where COMMAND is one of: start, stop, restart, healthcheck, create, create_core, create_collection, delete
@echo.
@echo Standalone server example (start Solr running in the background on port 8984):
@echo.
@ -215,6 +221,25 @@ echo bin\solr create_collection -help
echo.
goto done
:delete_usage
echo.
echo Usage: solr delete [-c name] [-deleteConfig boolean] [-p port]
echo.
echo Deletes a core or collection depending on whether Solr is running in standalone (core) or SolrCloud
echo mode (collection). If you're deleting a collection in SolrCloud mode, the default behavior is to also
echo delete the configuration directory from ZooKeeper so long as it is not being used by another collection.
echo You can override this behavior by passing -deleteConfig false when running this command.
echo.
echo -c name Name of core to create
echo.
echo -deleteConfig boolean Delete the configuration directory from ZooKeeper; default is true
echo.
echo -p port Port of a local Solr instance where you want to create the new core
echo If not specified, the script will search the local system for a running
echo Solr instance and will use the port of the first server it finds.
echo.
goto done
:create_core_usage
echo.
echo Usage: solr create_core [-c name] [-d confdir] [-p port]
@ -1142,6 +1167,72 @@ if "%SCRIPT_CMD%"=="create_core" (
goto done
:parse_delete_args
IF [%1]==[] goto run_delete
IF "%1"=="-c" goto set_delete_name
IF "%1"=="-core" goto set_delete_name
IF "%1"=="-collection" goto set_delete_name
IF "%1"=="-p" goto set_delete_port
IF "%1"=="-port" goto set_delete_port
IF "%1"=="-deleteConfig" goto set_delete_config
IF "%1"=="-help" goto usage
IF "%1"=="-usage" goto usage
IF "%1"=="/?" goto usage
goto run_delete
:set_delete_name
set DELETE_NAME=%~2
SHIFT
SHIFT
goto parse_delete_args
:set_delete_port
set DELETE_PORT=%~2
SHIFT
SHIFT
goto parse_delete_args
:set_delete_config
set DELETE_CONFIG=%~2
SHIFT
SHIFT
goto parse_delete_args
:run_delete
IF "!DELETE_NAME!"=="" (
set "SCRIPT_ERROR=Name (-c) is a required parameter for %SCRIPT_CMD%"
goto invalid_cmd_line
)
REM Find a port that Solr is running on
if "!DELETE_PORT!"=="" (
for /f "usebackq" %%i in (`dir /b %SOLR_TIP%\bin ^| findstr /i "^solr-.*\.port$"`) do (
set SOME_SOLR_PORT=
For /F "Delims=" %%J In (%SOLR_TIP%\bin\%%i) do set SOME_SOLR_PORT=%%~J
if NOT "!SOME_SOLR_PORT!"=="" (
for /f "tokens=2,5" %%j in ('netstat -aon ^| find /i "listening" ^| find /i "!SOME_SOLR_PORT!"') do (
set DELETE_PORT=!SOME_SOLR_PORT!
)
)
)
)
if "!DELETE_PORT!"=="" (
set "SCRIPT_ERROR=Could not find a running Solr instance on this host! Please use the -p option to specify the port."
goto err
)
if "!DELETE_CONFIG!"=="" (
set DELETE_CONFIG=true
)
"%JAVA%" %SOLR_SSL_OPTS% -Dsolr.install.dir="%SOLR_TIP%" -Dlog4j.configuration="file:%DEFAULT_SERVER_DIR%\scripts\cloud-scripts\log4j.properties" ^
-classpath "%DEFAULT_SERVER_DIR%\solr-webapp\webapp\WEB-INF\lib\*;%DEFAULT_SERVER_DIR%\lib\ext\*" ^
org.apache.solr.util.SolrCLI delete -name !DELETE_NAME! -deleteConfig !DELETE_CONFIG! ^
-solrUrl !SOLR_URL_SCHEME!://localhost:!DELETE_PORT!/solr
goto done
:invalid_cmd_line
@echo.
IF "!SCRIPT_ERROR!"=="" (

View File

@ -244,6 +244,8 @@ public class SolrCLI {
return new CreateCoreTool();
else if ("create".equals(toolType))
return new CreateTool();
else if ("delete".equals(toolType))
return new DeleteTool();
// If you add a built-in tool to this class, add it here to avoid
// classpath scanning
@ -265,6 +267,7 @@ public class SolrCLI {
formatter.printHelp("create_collection", getToolOptions(new CreateCollectionTool()));
formatter.printHelp("create_core", getToolOptions(new CreateCoreTool()));
formatter.printHelp("create", getToolOptions(new CreateTool()));
formatter.printHelp("delete", getToolOptions(new DeleteTool()));
List<Class<Tool>> toolClasses = findToolClassesInPackage("org.apache.solr.util");
for (Class<Tool> next : toolClasses) {
@ -1080,6 +1083,43 @@ public class SolrCLI {
.create("configsetsDir")
};
public static String getZkHost(CommandLine cli) throws Exception {
String zkHost = cli.getOptionValue("zkHost");
if (zkHost != null)
return zkHost;
// find it using the localPort
String solrUrl = cli.getOptionValue("solrUrl");
if (solrUrl == null)
throw new IllegalStateException(
"Must provide either the -zkHost or -solrUrl parameters to use the create_collection command!");
if (!solrUrl.endsWith("/"))
solrUrl += "/";
String systemInfoUrl = solrUrl+"admin/info/system";
CloseableHttpClient httpClient = getHttpClient();
try {
// hit Solr to get system info
Map<String,Object> systemInfo = getJson(httpClient, systemInfoUrl, 2);
// convert raw JSON into user-friendly output
StatusTool statusTool = new StatusTool();
Map<String,Object> status = statusTool.reportStatus(solrUrl, systemInfo, httpClient);
Map<String,Object> cloud = (Map<String, Object>)status.get("cloud");
if (cloud != null) {
String zookeeper = (String) cloud.get("ZooKeeper");
if (zookeeper.endsWith("(embedded)")) {
zookeeper = zookeeper.substring(0, zookeeper.length() - "(embedded)".length());
}
zkHost = zookeeper;
}
} finally {
HttpClientUtil.close(httpClient);
}
return zkHost;
}
/**
* Supports create_collection command in the bin/solr script.
@ -1103,43 +1143,12 @@ public class SolrCLI {
LogManager.getLogger("org.apache.zookeeper").setLevel(Level.ERROR);
LogManager.getLogger("org.apache.solr.common.cloud").setLevel(Level.WARN);
String zkHost = cli.getOptionValue("zkHost");
String zkHost = getZkHost(cli);
if (zkHost == null) {
// find it using the localPort
String solrUrl = cli.getOptionValue("solrUrl");
if (solrUrl == null)
throw new IllegalStateException(
"Must provide either the -zkHost or -solrUrl parameters to use the create_collection command!");
if (!solrUrl.endsWith("/"))
solrUrl += "/";
String systemInfoUrl = solrUrl+"admin/info/system";
CloseableHttpClient httpClient = getHttpClient();
try {
// hit Solr to get system info
Map<String,Object> systemInfo = getJson(httpClient, systemInfoUrl, 2);
// convert raw JSON into user-friendly output
StatusTool statusTool = new StatusTool();
Map<String,Object> status = statusTool.reportStatus(solrUrl, systemInfo, httpClient);
Map<String,Object> cloud = (Map<String, Object>)status.get("cloud");
if (cloud == null) {
System.err.println("\nERROR: Solr at "+solrUrl+
" is running in standalone server mode, please use the create_core command instead;\n" +
"create_collection can only be used when running in SolrCloud mode.\n");
return 1;
}
String zookeeper = (String) cloud.get("ZooKeeper");
if (zookeeper.endsWith("(embedded)")) {
zookeeper = zookeeper.substring(0,zookeeper.length()-"(embedded)".length());
}
zkHost = zookeeper;
} finally {
HttpClientUtil.close(httpClient);
}
System.err.println("\nERROR: Solr at "+cli.getOptionValue("solrUrl")+
" is running in standalone server mode, please use the create_core command instead;\n" +
"create_collection can only be used when running in SolrCloud mode.\n");
return 1;
}
int toolExitStatus = 0;
@ -1502,4 +1511,220 @@ public class SolrCLI {
} // end CreateTool class
public static class DeleteTool implements Tool {
@Override
public String getName() {
return "delete";
}
@SuppressWarnings("static-access")
@Override
public Option[] getOptions() {
return new Option[]{
OptionBuilder
.withArgName("URL")
.hasArg()
.isRequired(false)
.withDescription("Base Solr URL, default is http://localhost:8983/solr")
.create("solrUrl"),
OptionBuilder
.withArgName("NAME")
.hasArg()
.isRequired(true)
.withDescription("Name of the core / collection to delete.")
.create("name"),
OptionBuilder
.withArgName("true|false")
.hasArg()
.isRequired(false)
.withDescription("Flag to indicate if the underlying configuration directory for a collection should also be deleted; default is true")
.create("deleteConfig"),
OptionBuilder
.isRequired(false)
.withDescription("Skip safety checks when deleting the configuration directory used by a collection")
.create("forceDeleteConfig"),
OptionBuilder
.withArgName("HOST")
.hasArg()
.isRequired(false)
.withDescription("Address of the Zookeeper ensemble; defaults to: "+ZK_HOST)
.create("zkHost")
};
}
@Override
public int runTool(CommandLine cli) throws Exception {
// quiet down the ZK logging for cli tools
LogManager.getLogger("org.apache.zookeeper").setLevel(Level.ERROR);
LogManager.getLogger("org.apache.solr.common.cloud").setLevel(Level.WARN);
String solrUrl = cli.getOptionValue("solrUrl", "http://localhost:8983/solr");
if (!solrUrl.endsWith("/"))
solrUrl += "/";
String systemInfoUrl = solrUrl+"admin/info/system";
CloseableHttpClient httpClient = getHttpClient();
int result = 0;
try {
Map<String,Object> systemInfo = getJson(httpClient, systemInfoUrl, 2);
if ("solrcloud".equals(systemInfo.get("mode"))) {
result = deleteCollection(cli);
} else {
result = deleteCore(cli, httpClient, solrUrl);
}
} finally {
closeHttpClient(httpClient);
}
return result;
}
protected int deleteCollection(CommandLine cli) throws Exception {
String zkHost = getZkHost(cli);
int toolExitStatus = 0;
CloudSolrClient cloudSolrServer = null;
try {
cloudSolrServer = new CloudSolrClient(zkHost);
System.out.println("Connecting to ZooKeeper at " + zkHost);
cloudSolrServer.connect();
toolExitStatus = deleteCollection(cloudSolrServer, cli);
} catch (Exception exc) {
// since this is a CLI, spare the user the stacktrace
String excMsg = exc.getMessage();
if (excMsg != null) {
System.err.println("\nERROR: "+excMsg+"\n");
toolExitStatus = 1;
} else {
throw exc;
}
} finally {
if (cloudSolrServer != null) {
try {
cloudSolrServer.shutdown();
} catch (Exception ignore) {}
}
}
return toolExitStatus;
}
protected int deleteCollection(CloudSolrClient cloudSolrClient, CommandLine cli) throws Exception {
Set<String> liveNodes = cloudSolrClient.getZkStateReader().getClusterState().getLiveNodes();
if (liveNodes.isEmpty())
throw new IllegalStateException("No live nodes found! Cannot delete a collection until " +
"there is at least 1 live node in the cluster.");
String firstLiveNode = liveNodes.iterator().next();
ZkStateReader zkStateReader = cloudSolrClient.getZkStateReader();
String baseUrl = zkStateReader.getBaseUrlForNodeName(firstLiveNode);
String collectionName = cli.getOptionValue("name");
if (!zkStateReader.getClusterState().hasCollection(collectionName)) {
System.err.println("\nERROR: Collection "+collectionName+" not found!");
System.err.println();
return 1;
}
String configName = zkStateReader.readConfigName(collectionName);
boolean deleteConfig = "true".equals(cli.getOptionValue("deleteConfig", "true"));
if (deleteConfig && configName != null) {
if (cli.hasOption("forceDeleteConfig")) {
log.warn("Skipping safety checks, configuration directory "+configName+" will be deleted with impunity.");
} else {
// need to scan all Collections to see if any are using the config
Set<String> collections = zkStateReader.getClusterState().getCollections();
// give a little note to the user if there are many collections in case it takes a while
if (collections.size() > 50)
log.info("Scanning " + collections.size() +
" to ensure no other collections are using config " + configName);
for (String next : collections) {
if (collectionName.equals(next))
continue; // don't check the collection we're deleting
if (configName.equals(zkStateReader.readConfigName(next))) {
deleteConfig = false;
log.warn("Configuration directory "+configName+" is also being used by "+next+
"; configuration will not be deleted from ZooKeeper. You can pass the -forceDeleteConfig flag to force delete.");
break;
}
}
}
}
String deleteCollectionUrl =
String.format(Locale.ROOT,
"%s/admin/collections?action=DELETE&name=%s",
baseUrl,
collectionName);
System.out.println("\nDeleting collection '"+collectionName+"' using command:\n"+deleteCollectionUrl+"\n");
Map<String,Object> json = null;
try {
json = getJson(deleteCollectionUrl);
} catch (SolrServerException sse) {
System.err.println("Failed to delete collection '"+collectionName+"' due to: "+sse.getMessage());
System.err.println();
return 1;
}
if (deleteConfig) {
String configZnode = "/configs/" + configName;
try {
zkStateReader.getZkClient().clean(configZnode);
} catch (Exception exc) {
System.err.println("\nERROR: Failed to delete configuration directory "+configZnode+" in ZooKeeper due to: "+
exc.getMessage()+"\nYou'll need to manually delete this znode using the zkcli script.");
}
}
if (json != null) {
CharArr arr = new CharArr();
new JSONWriter(arr, 2).write(json);
System.out.println(arr.toString());
System.out.println();
}
return 0;
}
protected int deleteCore(CommandLine cli, CloseableHttpClient httpClient, String solrUrl) throws Exception {
int status = 0;
String coreName = cli.getOptionValue("name");
String deleteCoreUrl =
String.format(Locale.ROOT,
"%sadmin/cores?action=UNLOAD&core=%s&deleteIndex=true&deleteDataDir=true&deleteInstanceDir=true",
solrUrl,
coreName);
System.out.println("\nDeleting core '"+coreName+"' using command:\n"+deleteCoreUrl+"\n");
Map<String,Object> json = null;
try {
json = getJson(deleteCoreUrl);
} catch (SolrServerException sse) {
System.err.println("Failed to delete core '"+coreName+"' due to: "+sse.getMessage());
System.err.println();
status = 1;
}
if (json != null) {
CharArr arr = new CharArr();
new JSONWriter(arr, 2).write(json);
System.out.println(arr.toString());
System.out.println();
}
return status;
}
} // end DeleteTool class
}