All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.util.SolrCLI Maven / Gradle / Ivy

/*
 * 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.util;

import javax.net.ssl.SSLPeerUnverifiedException;
import java.io.Console;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.invoke.MethodHandles;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileOwnerAttributeView;
import java.time.Instant;
import java.time.Period;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.exec.DefaultExecuteResultHandler;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.OS;
import org.apache.commons.exec.environment.EnvironmentUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NoHttpResponseException;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.lucene.util.Version;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
import org.apache.solr.client.solrj.cloud.autoscaling.AutoScalingConfig;
import org.apache.solr.client.solrj.cloud.autoscaling.Policy;
import org.apache.solr.client.solrj.cloud.autoscaling.PolicyHelper;
import org.apache.solr.client.solrj.cloud.autoscaling.ReplicaInfo;
import org.apache.solr.client.solrj.cloud.autoscaling.Suggester;
import org.apache.solr.client.solrj.cloud.autoscaling.Variable;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.HttpClientUtil;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.impl.SolrClientCloudManager;
import org.apache.solr.client.solrj.impl.ZkClientClusterStateProvider;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.client.solrj.request.V2Request;
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.autoscaling.sim.NoopDistributedQueueFactory;
import org.apache.solr.cloud.autoscaling.sim.SimCloudManager;
import org.apache.solr.cloud.autoscaling.sim.SimScenario;
import org.apache.solr.cloud.autoscaling.sim.SimUtils;
import org.apache.solr.cloud.autoscaling.sim.SnapshotCloudManager;
import org.apache.solr.common.MapWriter;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.UrlScheme;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.ZkConfigManager;
import org.apache.solr.common.cloud.ZkCoreNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CollectionAdminParams;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.TimeSource;
import org.apache.solr.common.util.Utils;
import org.apache.solr.security.Sha256AuthenticationProvider;
import org.apache.solr.util.configuration.SSLConfigurationsFactory;
import org.noggit.CharArr;
import org.noggit.JSONParser;
import org.noggit.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.apache.solr.common.SolrException.ErrorCode.FORBIDDEN;
import static org.apache.solr.common.SolrException.ErrorCode.UNAUTHORIZED;
import static org.apache.solr.common.params.CommonParams.DISTRIB;
import static org.apache.solr.common.params.CommonParams.NAME;
import static org.apache.solr.common.util.Utils.fromJSONString;

/**
 * Command-line utility for working with Solr.
 */
public class SolrCLI {
  private static final long MAX_WAIT_FOR_CORE_LOAD_NANOS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.MINUTES);

  /**
   * Defines the interface to a Solr tool that can be run from this command-line app.
   */
  public interface Tool {
    String getName();
    Option[] getOptions();
    int runTool(CommandLine cli) throws Exception;
  }

  public static abstract class ToolBase implements Tool {
    protected PrintStream stdout;
    protected boolean verbose = false;

    protected ToolBase() {
      this(System.out);
    }

    protected ToolBase(PrintStream stdout) {
      this.stdout = stdout;
    }

    protected void echoIfVerbose(final String msg, CommandLine cli) {
      if (cli.hasOption("verbose")) {
        echo(msg);
      }
    }

    protected void echo(final String msg) {
      stdout.println(msg);
    }

    public int runTool(CommandLine cli) throws Exception {
      verbose = cli.hasOption("verbose");

      int toolExitStatus = 0;
      try {
        runImpl(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");
          if (verbose) {
            exc.printStackTrace(System.err);
          }
          toolExitStatus = 1;
        } else {
          throw exc;
        }
      }
      return toolExitStatus;
    }

    protected abstract void runImpl(CommandLine cli) throws Exception;
  }
  /**
   * Helps build SolrCloud aware tools by initializing a CloudSolrClient
   * instance before running the tool.
   */
  public static abstract class SolrCloudTool extends ToolBase {

    protected SolrCloudTool(PrintStream stdout) { super(stdout); }

    public Option[] getOptions() {
      return cloudOptions;
    }

    protected void runImpl(CommandLine cli) throws Exception {
      raiseLogLevelUnlessVerbose(cli);
      String zkHost = cli.getOptionValue("zkHost", ZK_HOST);

      log.debug("Connecting to Solr cluster: {}", zkHost);
      try (CloudSolrClient cloudSolrClient = new CloudSolrClient.Builder(Collections.singletonList(zkHost), Optional.empty()).build()) {

        String collection = cli.getOptionValue("collection");
        if (collection != null)
          cloudSolrClient.setDefaultCollection(collection);

        cloudSolrClient.connect();
        runCloudTool(cloudSolrClient, cli);
      }
    }

    /**
     * Runs a SolrCloud tool with CloudSolrClient initialized
     */
    protected abstract void runCloudTool(CloudSolrClient cloudSolrClient, CommandLine cli)
        throws Exception;
  }

  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
  public static final String DEFAULT_SOLR_URL = "http://localhost:8983/solr";
  public static final String ZK_HOST = "localhost:9983";

  public static Option[] cloudOptions =  new Option[] {
      Option.builder("zkHost")
          .argName("HOST")
          .hasArg()
          .required(false)
          .desc("Address of the ZooKeeper ensemble; defaults to: "+ ZK_HOST + '.')
          .build(),
      Option.builder("c")
          .argName("COLLECTION")
          .hasArg()
          .required(false)
          .desc("Name of collection; no default.")
          .longOpt("collection")
          .build(),
      Option.builder("verbose")
          .required(false)
          .desc("Enable more verbose command output.")
          .build()
  };

  private static void exit(int exitStatus) {
    try {
      System.exit(exitStatus);
    } catch (java.lang.SecurityException secExc) {
      if (exitStatus != 0)
        throw new RuntimeException("SolrCLI failed to exit with status "+exitStatus);
    }
  }

  /**
   * Runs a tool.
   */
  public static void main(String[] args) throws Exception {
    if (args == null || args.length == 0 || args[0] == null || args[0].trim().length() == 0) {
      System.err.println("Invalid command-line args! Must pass the name of a tool to run.\n"
          + "Supported tools:\n");
      displayToolOptions();
      exit(1);
    }

    if (args.length == 1 && Arrays.asList("-v","-version","version").contains(args[0])) {
      // Simple version tool, no need for its own class
      System.out.println(Version.LATEST);
      exit(0);
    }

    SSLConfigurationsFactory.current().init();

    Tool tool = findTool(args);
    CommandLine cli = parseCmdLine(args, tool.getOptions());
    System.exit(tool.runTool(cli));
  }

  public static Tool findTool(String[] args) throws Exception {
    String toolType = args[0].trim().toLowerCase(Locale.ROOT);
    return newTool(toolType);
  }

  public static CommandLine parseCmdLine(String[] args, Option[] toolOptions) throws Exception {
    // the parser doesn't like -D props
    List toolArgList = new ArrayList();
    List dashDList = new ArrayList();
    for (int a=1; a < args.length; a++) {
      String arg = args[a];
      if (arg.startsWith("-D")) {
        dashDList.add(arg);
      } else {
        toolArgList.add(arg);
      }
    }
    String[] toolArgs = toolArgList.toArray(new String[0]);

    // process command-line args to configure this application
    CommandLine cli =
        processCommandLineArgs(joinCommonAndToolOptions(toolOptions), toolArgs);

    List argList = cli.getArgList();
    argList.addAll(dashDList);

    // for SSL support, try to accommodate relative paths set for SSL store props
    String solrInstallDir = System.getProperty("solr.install.dir");
    if (solrInstallDir != null) {
      checkSslStoreSysProp(solrInstallDir, "keyStore");
      checkSslStoreSysProp(solrInstallDir, "trustStore");
    }

    return cli;
  }

  protected static void checkSslStoreSysProp(String solrInstallDir, String key) {
    String sysProp = "javax.net.ssl."+key;
    String keyStore = System.getProperty(sysProp);
    if (keyStore == null)
      return;

    File keyStoreFile = new File(keyStore);
    if (keyStoreFile.isFile())
      return; // configured setting is OK

    keyStoreFile = new File(solrInstallDir, "server/"+keyStore);
    if (keyStoreFile.isFile()) {
      System.setProperty(sysProp, keyStoreFile.getAbsolutePath());
    } else {
      System.err.println("WARNING: "+sysProp+" file "+keyStore+
          " not found! https requests to Solr will likely fail; please update your "+
          sysProp+" setting to use an absolute path.");
    }
  }

  private static void raiseLogLevelUnlessVerbose(CommandLine cli) {
    if (! cli.hasOption("verbose")) {
      StartupLoggingUtils.changeLogLevel("WARN");
    }
  }

  /**
   * Support options common to all tools.
   */
  public static Option[] getCommonToolOptions() {
    return new Option[0];
  }

  // Creates an instance of the requested tool, using classpath scanning if necessary
  private static Tool newTool(String toolType) throws Exception {
    if ("healthcheck".equals(toolType))
      return new HealthcheckTool();
    else if ("status".equals(toolType))
      return new StatusTool();
    else if ("api".equals(toolType))
      return new ApiTool();
    else if ("create_collection".equals(toolType))
      return new CreateCollectionTool();
    else if ("create_core".equals(toolType))
      return new CreateCoreTool();
    else if ("create".equals(toolType))
      return new CreateTool();
    else if ("delete".equals(toolType))
      return new DeleteTool();
    else if ("config".equals(toolType))
      return new ConfigTool();
    else if ("run_example".equals(toolType))
      return new RunExampleTool();
    else if ("upconfig".equals(toolType))
      return new ConfigSetUploadTool();
    else if ("downconfig".equals(toolType))
      return new ConfigSetDownloadTool();
    else if ("rm".equals(toolType))
      return new ZkRmTool();
    else if ("mv".equals(toolType))
      return new ZkMvTool();
    else if ("cp".equals(toolType))
      return new ZkCpTool();
    else if ("ls".equals(toolType))
      return new ZkLsTool();
    else if ("mkroot".equals(toolType))
      return new ZkMkrootTool();
    else if ("assert".equals(toolType))
      return new AssertTool();
    else if ("utils".equals(toolType))
      return new UtilsTool();
    else if ("auth".equals(toolType))
      return new AuthTool();
    else if ("autoscaling".equals(toolType))
      return new AutoscalingTool();
    else if ("export".equals(toolType))
      return new ExportTool();
    else if ("package".equals(toolType))
      return new PackageTool();

    // If you add a built-in tool to this class, add it here to avoid
    // classpath scanning

    for (Class next : findToolClassesInPackage("org.apache.solr.util")) {
      Tool tool = next.newInstance();
      if (toolType.equals(tool.getName()))
        return tool;
    }

    throw new IllegalArgumentException(toolType + " not supported!");
  }

  private static void displayToolOptions() throws Exception {
    HelpFormatter formatter = new HelpFormatter();
    formatter.printHelp("healthcheck", getToolOptions(new HealthcheckTool()));
    formatter.printHelp("status", getToolOptions(new StatusTool()));
    formatter.printHelp("api", getToolOptions(new ApiTool()));
    formatter.printHelp("autoscaling", getToolOptions(new AutoscalingTool()));
    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()));
    formatter.printHelp("config", getToolOptions(new ConfigTool()));
    formatter.printHelp("run_example", getToolOptions(new RunExampleTool()));
    formatter.printHelp("upconfig", getToolOptions(new ConfigSetUploadTool()));
    formatter.printHelp("downconfig", getToolOptions(new ConfigSetDownloadTool()));
    formatter.printHelp("rm", getToolOptions(new ZkRmTool()));
    formatter.printHelp("cp", getToolOptions(new ZkCpTool()));
    formatter.printHelp("mv", getToolOptions(new ZkMvTool()));
    formatter.printHelp("ls", getToolOptions(new ZkLsTool()));
    formatter.printHelp("package", getToolOptions(new PackageTool()));

    List> toolClasses = findToolClassesInPackage("org.apache.solr.util");
    for (Class next : toolClasses) {
      Tool tool = next.newInstance();
      formatter.printHelp(tool.getName(), getToolOptions(tool));
    }
  }

  private static Options getToolOptions(Tool tool) {
    Options options = new Options();
    options.addOption("help", false, "Print this message");
    options.addOption("verbose", false, "Generate verbose log messages");
    Option[] toolOpts = joinCommonAndToolOptions(tool.getOptions());
    for (int i = 0; i < toolOpts.length; i++)
      options.addOption(toolOpts[i]);
    return options;
  }

  public static Option[] joinCommonAndToolOptions(Option[] toolOpts) {
    return joinOptions(getCommonToolOptions(), toolOpts);
  }

  public static Option[] joinOptions(Option[] lhs, Option[] rhs) {
    List