You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ep...@apache.org on 2023/06/22 12:18:46 UTC

[solr] branch main updated: SOLR-16831: Fix the bin/solr healthcheck command. (#1710)

This is an automated email from the ASF dual-hosted git repository.

epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 81e54fbf705 SOLR-16831: Fix the bin/solr healthcheck command. (#1710)
81e54fbf705 is described below

commit 81e54fbf705320d7a9a30469cf4498af96203b34
Author: Eric Pugh <ep...@opensourceconnections.com>
AuthorDate: Thu Jun 22 08:18:39 2023 -0400

    SOLR-16831: Fix the bin/solr healthcheck command. (#1710)
    
    Fix healthcheck CLI command, and add the ability to take in a -solrUrl as well as -zkHost.   As part of this cleanup, discovered that SolrCloudTool.java was ONLY used by the Healthcheck command, and it actually wasn't helpful, so removed this class.
---
 solr/CHANGES.txt                                   |  2 +
 .../java/org/apache/solr/cli/HealthcheckTool.java  | 68 +++++++++++++++-----
 .../java/org/apache/solr/cli/SolrCloudTool.java    | 66 -------------------
 .../org/apache/solr/cli/HealthcheckToolTest.java   | 74 ++++++++++++++++++++++
 solr/packaging/test/test_healthcheck.bats          | 36 +++++++++++
 .../pages/solr-control-script-reference.adoc       |  9 +++
 6 files changed, 173 insertions(+), 82 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index e496d71f78e..9a472b633eb 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -239,6 +239,8 @@ Bug Fixes
 
 * SOLR-16807: DenseVectorField breaks catch-all copyFields (Joel Bernstein)
 
+* SOLR-16831: Fixed bin/solr healthcheck command returning cluster status details. Removed SolrCloudTool class that is no longer used. (Eric Pugh)
+
 Dependency Upgrades
 ---------------------
 * PR#1494: Upgrade forbiddenapis to 3.5 (Uwe Schindler)
diff --git a/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java b/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java
index 9d3dbb9927f..c3712d29131 100644
--- a/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java
@@ -24,13 +24,17 @@ import java.io.PrintStream;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.impl.CloudHttp2SolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
@@ -47,9 +51,23 @@ import org.noggit.JSONWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class HealthcheckTool extends SolrCloudTool {
+public class HealthcheckTool extends ToolBase {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
+  @Override
+  public List<Option> getOptions() {
+    return List.of(
+        SolrCLI.OPTION_SOLRURL,
+        SolrCLI.OPTION_ZKHOST,
+        Option.builder("c")
+            .argName("COLLECTION")
+            .hasArg()
+            .required(false)
+            .desc("Name of collection; no default.")
+            .longOpt("collection")
+            .build());
+  }
+
   enum ShardState {
     healthy,
     degraded,
@@ -66,17 +84,30 @@ public class HealthcheckTool extends SolrCloudTool {
     super(stdout);
   }
 
+  @Override
+  public void runImpl(CommandLine cli) throws Exception {
+    SolrCLI.raiseLogLevelUnlessVerbose(cli);
+    String zkHost = SolrCLI.getZkHost(cli);
+    try (CloudHttp2SolrClient cloudSolrClient =
+        new CloudHttp2SolrClient.Builder(Collections.singletonList(zkHost), Optional.empty())
+            .build()) {
+      echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ...", cli);
+      cloudSolrClient.connect();
+      runCloudTool(cloudSolrClient, cli);
+    }
+  }
+
   @Override
   public String getName() {
     return "healthcheck";
   }
 
-  @Override
   protected void runCloudTool(CloudSolrClient cloudSolrClient, CommandLine cli) throws Exception {
     SolrCLI.raiseLogLevelUnlessVerbose(cli);
     String collection = cli.getOptionValue("collection");
-    if (collection == null)
+    if (collection == null) {
       throw new IllegalArgumentException("Must provide a collection to run a healthcheck against!");
+    }
 
     log.debug("Running healthcheck for {}", collection);
 
@@ -85,8 +116,9 @@ public class HealthcheckTool extends SolrCloudTool {
     ClusterState clusterState = zkStateReader.getClusterState();
     Set<String> liveNodes = clusterState.getLiveNodes();
     final DocCollection docCollection = clusterState.getCollectionOrNull(collection);
-    if (docCollection == null || docCollection.getSlices() == null)
+    if (docCollection == null || docCollection.getSlices() == null) {
       throw new IllegalArgumentException("Collection " + collection + " not found!");
+    }
 
     Collection<Slice> slices = docCollection.getSlices();
 
@@ -136,17 +168,20 @@ public class HealthcheckTool extends SolrCloudTool {
           q = new SolrQuery("*:*");
           q.setRows(0);
           q.set(DISTRIB, "false");
-          try (var solrClient = SolrCLI.getSolrClient(coreUrl)) {
-            qr = solrClient.query(q);
+          try (var solrClientForCollection = SolrCLI.getSolrClient(coreUrl)) {
+            qr = solrClientForCollection.query(q);
             numDocs = qr.getResults().getNumFound();
-
-            NamedList<Object> systemInfo =
-                solrClient.request(
-                    new GenericSolrRequest(SolrRequest.METHOD.GET, CommonParams.SYSTEM_INFO_PATH));
-            uptime = SolrCLI.uptime((Long) systemInfo.findRecursive("jvm", "jmx", "upTimeMS"));
-            String usedMemory = (String) systemInfo.findRecursive("jvm", "memory", "used");
-            String totalMemory = (String) systemInfo.findRecursive("jvm", "memory", "total");
-            memory = usedMemory + " of " + totalMemory;
+            try (var solrClient = SolrCLI.getSolrClient(replicaCoreProps.getBaseUrl())) {
+              NamedList<Object> systemInfo =
+                  solrClient.request(
+                      new GenericSolrRequest(
+                          SolrRequest.METHOD.GET, CommonParams.SYSTEM_INFO_PATH));
+              System.out.println("did this work?");
+              uptime = SolrCLI.uptime((Long) systemInfo.findRecursive("jvm", "jmx", "upTimeMS"));
+              String usedMemory = (String) systemInfo.findRecursive("jvm", "memory", "used");
+              String totalMemory = (String) systemInfo.findRecursive("jvm", "memory", "total");
+              memory = usedMemory + " of " + totalMemory;
+            }
 
             // if we get here, we can trust the state
             replicaStatus = replicaCoreProps.getState();
@@ -167,8 +202,9 @@ public class HealthcheckTool extends SolrCloudTool {
       }
 
       ShardHealth shardHealth = new ShardHealth(shardName, replicaList);
-      if (ShardState.healthy != shardHealth.getShardState())
+      if (ShardState.healthy != shardHealth.getShardState()) {
         collectionIsHealthy = false; // at least one shard is un-healthy
+      }
 
       shardList.add(shardHealth.asMap());
     }
@@ -187,7 +223,7 @@ public class HealthcheckTool extends SolrCloudTool {
     new JSONWriter(arr, 2).write(report);
     echo(arr.toString());
   }
-} // end HealthcheckTool
+}
 
 class ReplicaHealth implements Comparable<ReplicaHealth> {
   String shard;
diff --git a/solr/core/src/java/org/apache/solr/cli/SolrCloudTool.java b/solr/core/src/java/org/apache/solr/cli/SolrCloudTool.java
deleted file mode 100644
index 7946efb1d28..00000000000
--- a/solr/core/src/java/org/apache/solr/cli/SolrCloudTool.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.cli;
-
-import java.io.PrintStream;
-import java.lang.invoke.MethodHandles;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.Option;
-import org.apache.solr.client.solrj.impl.CloudHttp2SolrClient;
-import org.apache.solr.client.solrj.impl.CloudSolrClient;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Helps build SolrCloud aware tools by initializing a CloudSolrClient instance before running the
- * tool.
- */
-public abstract class SolrCloudTool extends ToolBase {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  protected SolrCloudTool(PrintStream stdout) {
-    super(stdout);
-  }
-
-  @Override
-  public List<Option> getOptions() {
-    return SolrCLI.cloudOptions;
-  }
-
-  @Override
-  public void runImpl(CommandLine cli) throws Exception {
-    SolrCLI.raiseLogLevelUnlessVerbose(cli);
-    String zkHost = cli.getOptionValue(SolrCLI.OPTION_ZKHOST.getOpt(), SolrCLI.ZK_HOST);
-
-    log.debug("Connecting to Solr cluster: {}", zkHost);
-    try (var cloudSolrClient =
-        new CloudHttp2SolrClient.Builder(Collections.singletonList(zkHost), Optional.empty())
-            .build()) {
-
-      cloudSolrClient.connect();
-      runCloudTool(cloudSolrClient, cli);
-    }
-  }
-
-  /** Runs a SolrCloud tool with CloudSolrClient initialized */
-  protected abstract void runCloudTool(CloudSolrClient cloudSolrClient, CommandLine cli)
-      throws Exception;
-}
diff --git a/solr/core/src/test/org/apache/solr/cli/HealthcheckToolTest.java b/solr/core/src/test/org/apache/solr/cli/HealthcheckToolTest.java
new file mode 100644
index 00000000000..3826523db79
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cli/HealthcheckToolTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.cli;
+
+import static org.apache.solr.cli.SolrCLI.findTool;
+import static org.apache.solr.cli.SolrCLI.parseCmdLine;
+
+import java.util.Set;
+import org.apache.commons.cli.CommandLine;
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class HealthcheckToolTest extends SolrCloudTestCase {
+
+  @BeforeClass
+  public static void setupCluster() throws Exception {
+    configureCluster(1)
+        .addConfig(
+            "config", TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
+        .configure();
+
+    CloudSolrClient cloudSolrClient = cluster.getSolrClient();
+
+    CollectionAdminRequest.createCollection("bob", 1, 1).process(cloudSolrClient);
+  }
+
+  @Test
+  public void testHealthcheckWithZkHostParameter() throws Exception {
+
+    String[] args =
+        new String[] {
+          "healthcheck", "-collection", "bob", "-zkHost", cluster.getZkClient().getZkServerAddress()
+        };
+    assertEquals(0, runTool(args));
+  }
+
+  @Test
+  public void testHealthcheckWithSolrUrlParameter() throws Exception {
+
+    Set<String> liveNodes = cluster.getSolrClient().getClusterState().getLiveNodes();
+    String firstLiveNode = liveNodes.iterator().next();
+    String solrUrl =
+        ZkStateReader.from(cluster.getSolrClient()).getBaseUrlForNodeName(firstLiveNode);
+
+    String[] args = new String[] {"healthcheck", "-collection", "bob", "-solrUrl", solrUrl};
+    assertEquals(0, runTool(args));
+  }
+
+  private int runTool(String[] args) throws Exception {
+    Tool tool = findTool(args);
+    assertTrue(tool instanceof HealthcheckTool);
+    CommandLine cli = parseCmdLine(tool.getName(), args, tool.getOptions());
+    return tool.runTool(cli);
+  }
+}
diff --git a/solr/packaging/test/test_healthcheck.bats b/solr/packaging/test/test_healthcheck.bats
new file mode 100644
index 00000000000..73c41405653
--- /dev/null
+++ b/solr/packaging/test/test_healthcheck.bats
@@ -0,0 +1,36 @@
+#!/usr/bin/env bats
+
+# 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.
+
+load bats_helper
+
+setup() {
+  common_clean_setup
+}
+
+teardown() {
+  # save a snapshot of SOLR_HOME for failed tests
+  save_home_on_failure
+
+  solr stop -all >/dev/null 2>&1
+}
+
+@test "healthcheck on cloud solr" {
+  solr start -c -e films
+  run solr healthcheck -c films
+  refute_output --partial 'error'
+  
+}
diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc
index b5be509ae78..5c20a5197ae 100644
--- a/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc
+++ b/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc
@@ -549,6 +549,15 @@ Name of the collection to run a healthcheck against.
 +
 *Example*: `bin/solr healthcheck -c gettingstarted`
 
+`-solrUrl <url>`::
++
+[%autowidth,frame=none]
+|===
+|Optional |Default: none
+|===
++
+Base Solr URL, which can be used in SolrCloud mode to determine the ZooKeeper connection string if that's not known.
+
 `-z <zkhost>` or `-zkHost <zkhost>`::
 +
 [%autowidth,frame=none]