You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by sh...@apache.org on 2019/01/06 07:12:03 UTC

lucene-solr:master: SOLR-11126: New Node-level health check handler at /admin/info/healthcheck and /node/health paths that checks if the node is live, connected to zookeeper and not shutdown

Repository: lucene-solr
Updated Branches:
  refs/heads/master 46592e981 -> 2bd6f246b


SOLR-11126: New Node-level health check handler at /admin/info/healthcheck and /node/health paths that checks if the node is live, connected to zookeeper and not shutdown


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/2bd6f246
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/2bd6f246
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/2bd6f246

Branch: refs/heads/master
Commit: 2bd6f246b026b7d576097a4e4c3ebb293fc2b0d0
Parents: 46592e9
Author: Shalin Shekhar Mangar <sh...@apache.org>
Authored: Sun Jan 6 12:41:49 2019 +0530
Committer: Shalin Shekhar Mangar <sh...@apache.org>
Committed: Sun Jan 6 12:41:49 2019 +0530

----------------------------------------------------------------------
 solr/CHANGES.txt                                |  3 +
 .../org/apache/solr/core/CoreContainer.java     |  2 -
 .../solr/handler/admin/HealthCheckHandler.java  | 22 +++--
 .../apache/solr/handler/admin/InfoHandler.java  |  2 +
 solr/core/src/resources/ImplicitPlugins.json    |  4 +
 .../solr/cloud/HealthCheckHandlerTest.java      | 94 +++++++++++++++++---
 .../test/org/apache/solr/core/SolrCoreTest.java |  1 +
 .../src/implicit-requesthandlers.adoc           | 12 +++
 .../apache/solr/common/params/CommonParams.java |  2 +-
 solr/solrj/src/resources/apispec/node.Info.json |  5 +-
 10 files changed, 121 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2bd6f246/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index be1195b..58a8edd 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -185,6 +185,9 @@ New Features
 
 * SOLR-7896: Add a login page to Admin UI, with initial support for Basic Auth (janhoy)
 
+* SOLR-11126: New Node-level health check handler at /admin/info/healthcheck and /node/health paths that
+  checks if the node is live, connected to zookeeper and not shutdown. (Anshum Gupta, Amrit Sarkar, shalin)
+
 Bug Fixes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2bd6f246/solr/core/src/java/org/apache/solr/core/CoreContainer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 5ce3c06..3592c6e 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -21,7 +21,6 @@ import static org.apache.solr.common.params.CommonParams.AUTHC_PATH;
 import static org.apache.solr.common.params.CommonParams.AUTHZ_PATH;
 import static org.apache.solr.common.params.CommonParams.AUTOSCALING_HISTORY_PATH;
 import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PATH;
-import static org.apache.solr.common.params.CommonParams.HEALTH_CHECK_HANDLER_PATH;
 import static org.apache.solr.common.params.CommonParams.CONFIGSETS_HANDLER_PATH;
 import static org.apache.solr.common.params.CommonParams.CORES_HANDLER_PATH;
 import static org.apache.solr.common.params.CommonParams.INFO_HANDLER_PATH;
@@ -576,7 +575,6 @@ public class CoreContainer {
     createHandler(ZK_PATH, ZookeeperInfoHandler.class.getName(), ZookeeperInfoHandler.class);
     createHandler(ZK_STATUS_PATH, ZookeeperStatusHandler.class.getName(), ZookeeperStatusHandler.class);
     collectionsHandler = createHandler(COLLECTIONS_HANDLER_PATH, cfg.getCollectionsHandlerClass(), CollectionsHandler.class);
-    healthCheckHandler = createHandler(HEALTH_CHECK_HANDLER_PATH, cfg.getHealthCheckHandlerClass(), HealthCheckHandler.class);
     infoHandler        = createHandler(INFO_HANDLER_PATH, cfg.getInfoHandlerClass(), InfoHandler.class);
     coreAdminHandler   = createHandler(CORES_HANDLER_PATH, cfg.getCoreAdminHandlerClass(), CoreAdminHandler.class);
     configSetsHandler = createHandler(CONFIGSETS_HANDLER_PATH, cfg.getConfigSetsHandlerClass(), ConfigSetsHandler.class);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2bd6f246/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java
index 7b07a1e..c496aaf 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java
@@ -18,7 +18,6 @@
 package org.apache.solr.handler.admin;
 
 import java.lang.invoke.MethodHandles;
-
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.ZkStateReader;
@@ -30,6 +29,7 @@ import org.apache.solr.response.SolrQueryResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+
 import static org.apache.solr.common.params.CommonParams.FAILURE;
 import static org.apache.solr.common.params.CommonParams.OK;
 import static org.apache.solr.common.params.CommonParams.STATUS;
@@ -38,8 +38,9 @@ import static org.apache.solr.common.params.CommonParams.STATUS;
  * Health Check Handler for reporting the health of a specific node.
  *
  * This checks if the node is:
- * 1. Connected to zookeeper
- * 2. listed in 'live_nodes'.
+ * 1. Cores container is active.
+ * 1. Connected to zookeeper.
+ * 2. Listed in 'live_nodes' in zookeeper.
  */
 public class HealthCheckHandler extends RequestHandlerBase {
 
@@ -47,6 +48,8 @@ public class HealthCheckHandler extends RequestHandlerBase {
 
   CoreContainer coreContainer;
 
+  public HealthCheckHandler() {}
+
   public HealthCheckHandler(final CoreContainer coreContainer) {
     super();
     this.coreContainer = coreContainer;
@@ -54,7 +57,6 @@ public class HealthCheckHandler extends RequestHandlerBase {
 
   @Override
   final public void init(NamedList args) {
-
   }
 
   public CoreContainer getCoreContainer() {
@@ -67,8 +69,9 @@ public class HealthCheckHandler extends RequestHandlerBase {
     log.debug("Invoked HealthCheckHandler on [{}]", coreContainer.getZkController().getNodeName());
     CoreContainer cores = getCoreContainer();
 
-    if(cores == null) {
-      rsp.setException(new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Core container not initialized"));
+    // Core container should not be null and active (redundant check)
+    if(cores == null || cores.isShutDown()) {
+      rsp.setException(new SolrException(SolrException.ErrorCode.SERVER_ERROR, "CoreContainer is either not initialized or shutting down"));
       return;
     }
     if(!cores.isZooKeeperAware()) {
@@ -94,8 +97,6 @@ public class HealthCheckHandler extends RequestHandlerBase {
     }
 
     rsp.setHttpCaching(false);
-
-    return;
   }
 
   @Override
@@ -107,4 +108,9 @@ public class HealthCheckHandler extends RequestHandlerBase {
   public Category getCategory() {
     return Category.ADMIN;
   }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2bd6f246/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
index a2bfb5b..b0c6d61 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
@@ -50,6 +50,8 @@ public class InfoHandler extends RequestHandlerBase  {
     handlers.put("properties", new PropertiesRequestHandler());
     handlers.put("logging", new LoggingHandler(coreContainer));
     handlers.put("system", new SystemInfoHandler(coreContainer));
+    handlers.put("health", new HealthCheckHandler(coreContainer));
+
   }
 
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2bd6f246/solr/core/src/resources/ImplicitPlugins.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/ImplicitPlugins.json b/solr/core/src/resources/ImplicitPlugins.json
index a1ddbe7..dc66496 100644
--- a/solr/core/src/resources/ImplicitPlugins.json
+++ b/solr/core/src/resources/ImplicitPlugins.json
@@ -91,6 +91,10 @@
       "class": "solr.LoggingHandler",
       "useParams":"_ADMIN_LOGGING"
     },
+    "/admin/health": {
+      "class": "solr.HealthCheckHandler",
+      "useParams":"_ADMIN_HEALTH"
+    },
     "/admin/file": {
       "class": "solr.ShowFileRequestHandler",
       "useParams":"_ADMIN_FILE"

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2bd6f246/solr/core/src/test/org/apache/solr/cloud/HealthCheckHandlerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/HealthCheckHandlerTest.java b/solr/core/src/test/org/apache/solr/cloud/HealthCheckHandlerTest.java
index 975009c..d9ebe74 100644
--- a/solr/core/src/test/org/apache/solr/cloud/HealthCheckHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/HealthCheckHandlerTest.java
@@ -24,13 +24,16 @@ import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.request.HealthCheckRequest;
+import org.apache.solr.client.solrj.request.V2Request;
+import org.apache.solr.client.solrj.response.CollectionAdminResponse;
 import org.apache.solr.client.solrj.response.HealthCheckResponse;
+import org.apache.solr.client.solrj.response.V2Response;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
-
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -53,42 +56,78 @@ public class HealthCheckHandlerTest extends SolrCloudTestCase {
     // as compared with testHealthCheckHandlerWithCloudClient
     // (Not sure if that's actaully a good thing -- but it's how the existing test worked)
     assertEquals(CommonParams.OK,
-                 req.process(cluster.getSolrClient()).getResponse().get(CommonParams.STATUS));
-    
+        req.process(cluster.getSolrClient()).getResponse().get(CommonParams.STATUS));
+
     // positive check that our exiting "healthy" node works with direct http client
     try (HttpSolrClient httpSolrClient = getHttpSolrClient(cluster.getJettySolrRunner(0).getBaseUrl().toString())) {
       SolrResponse response = req.process(httpSolrClient);
       assertEquals(CommonParams.OK, response.getResponse().get(CommonParams.STATUS));
     }
 
+    // successfully create a dummy collection
+    try (HttpSolrClient httpSolrClient = getHttpSolrClient(cluster.getJettySolrRunner(0).getBaseUrl().toString())) {
+      CollectionAdminResponse collectionAdminResponse = CollectionAdminRequest.createCollection("test", "_default", 1, 1)
+          .withProperty("solr.directoryFactory", "solr.StandardDirectoryFactory")
+          .process(httpSolrClient);
+      assertEquals(0, collectionAdminResponse.getStatus());
+      SolrResponse response = req.process(httpSolrClient);
+      assertEquals(CommonParams.OK, response.getResponse().get(CommonParams.STATUS));
+    } finally {
+      cluster.deleteAllCollections();
+      cluster.deleteAllConfigSets();
+    }
+
     // add a new node for the purpose of negative testing
     JettySolrRunner newJetty = cluster.startJettySolrRunner();
     try (HttpSolrClient httpSolrClient = getHttpSolrClient(newJetty.getBaseUrl().toString())) {
-    
+
       // postive check that our (new) "healthy" node works with direct http client
       assertEquals(CommonParams.OK, req.process(httpSolrClient).getResponse().get(CommonParams.STATUS));
-                  
+
       // now "break" our (new) node
       newJetty.getCoreContainer().getZkController().getZkClient().close();
-      
+
       // negative check of our (new) "broken" node that we deliberately put into an unhealth state
       HttpSolrClient.RemoteSolrException e = expectThrows(HttpSolrClient.RemoteSolrException.class, () ->
-                                                          {
-                                                            req.process(httpSolrClient);
-                                                          });
+      {
+        req.process(httpSolrClient);
+      });
       assertTrue(e.getMessage(), e.getMessage().contains("Host Unavailable"));
       assertEquals(SolrException.ErrorCode.SERVICE_UNAVAILABLE.code, e.code());
     } finally {
       newJetty.stop();
     }
 
+    // add a new node for the purpose of negative testing
+    // negative check that if core container is not available at the node
+    newJetty = cluster.startJettySolrRunner();
+    try (HttpSolrClient httpSolrClient = getHttpSolrClient(newJetty.getBaseUrl().toString())) {
+
+      // postive check that our (new) "healthy" node works with direct http client
+      assertEquals(CommonParams.OK, req.process(httpSolrClient).getResponse().get(CommonParams.STATUS));
+
+      // shutdown the core container of new node
+      newJetty.getCoreContainer().shutdown();
+
+      // api shouldn't unreachable
+      SolrException thrown = expectThrows(SolrException.class, () -> {
+        req.process(httpSolrClient).getResponse().get(CommonParams.STATUS);
+        fail("API shouldn't be available, and fail at above request");
+      });
+      assertEquals("Exception code should be 404", 404, thrown.code());
+      assertTrue("Should have seen an exception containing the an error", thrown.getMessage().contains(
+          "Error processing the request. CoreContainer is either not initialized or shutting down."));
+    } finally {
+      newJetty.stop();
+    }
+
     // (redundent) positive check that our (previously) exiting "healthy" node (still) works
-    // after getting negative results from our broken node
+    // after getting negative results from our broken node and failed core container
     try (HttpSolrClient httpSolrClient = getHttpSolrClient(cluster.getJettySolrRunner(0).getBaseUrl().toString())) {
 
       assertEquals(CommonParams.OK, req.process(httpSolrClient).getResponse().get(CommonParams.STATUS));
     }
-    
+
   }
 
   @Test
@@ -101,11 +140,40 @@ public class HealthCheckHandlerTest extends SolrCloudTestCase {
     }
   }
 
-  @Test (expected = AssertionError.class)
+  @Test(expected = AssertionError.class)
   public void testHealthCheckHandlerWithCloudClient() throws IOException, SolrServerException {
     // negative check of a HealthCheckRequest using cloud solr client
     HealthCheckRequest req = new HealthCheckRequest();
     req.process(cluster.getSolrClient());
   }
 
-}
+  @Test
+  public void testHealthCheckV2Api() throws Exception {
+    V2Response res = new V2Request.Builder("/node/health").build().process(cluster.getSolrClient());
+    assertEquals(0, res.getStatus());
+    assertEquals(CommonParams.OK, res.getResponse().get(CommonParams.STATUS));
+
+    // add a new node for the purpose of negative testing
+    JettySolrRunner newJetty = cluster.startJettySolrRunner();
+    try (HttpSolrClient httpSolrClient = getHttpSolrClient(newJetty.getBaseUrl().toString())) {
+
+      // postive check that our (new) "healthy" node works with direct http client
+      assertEquals(CommonParams.OK, new V2Request.Builder("/node/health").build().process(httpSolrClient).
+          getResponse().get(CommonParams.STATUS));
+
+      // now "break" our (new) node
+      newJetty.getCoreContainer().getZkController().getZkClient().close();
+
+      // negative check of our (new) "broken" node that we deliberately put into an unhealth state
+      HttpSolrClient.RemoteSolrException e = expectThrows(HttpSolrClient.RemoteSolrException.class, () ->
+      {
+        new V2Request.Builder("/node/health").build().process(httpSolrClient);
+      });
+      assertTrue(e.getMessage(), e.getMessage().contains("Host Unavailable"));
+      assertEquals(SolrException.ErrorCode.SERVICE_UNAVAILABLE.code, e.code());
+    } finally {
+      newJetty.stop();
+    }
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2bd6f246/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java b/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
index 724799e..d9411ba 100644
--- a/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
+++ b/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
@@ -87,6 +87,7 @@ public class SolrCoreTest extends SolrTestCaseJ4 {
 
     int ihCount = 0;
     {
+      ++ihCount; assertEquals(pathToClassMap.get("/admin/health"), "solr.HealthCheckHandler");
       ++ihCount; assertEquals(pathToClassMap.get("/admin/file"), "solr.ShowFileRequestHandler");
       ++ihCount; assertEquals(pathToClassMap.get("/admin/logging"), "solr.LoggingHandler");
       ++ihCount; assertEquals(pathToClassMap.get("/admin/luke"), "solr.LukeRequestHandler");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2bd6f246/solr/solr-ref-guide/src/implicit-requesthandlers.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/implicit-requesthandlers.adoc b/solr/solr-ref-guide/src/implicit-requesthandlers.adoc
index 622fbd5..6a1c0a3 100644
--- a/solr/solr-ref-guide/src/implicit-requesthandlers.adoc
+++ b/solr/solr-ref-guide/src/implicit-requesthandlers.adoc
@@ -128,6 +128,18 @@ Threads:: Return info on all JVM threads.
 v2: `api/node/threads` |{solr-javadocs}/solr-core/org/apache/solr/handler/admin/ThreadDumpHandler.html[ThreadDumpHandler] |`_ADMIN_THREADS`
 |===
 
+Health:: Reporting the health of the node (_available only in Solrcloud mode_)
++
+[cols="3*.",frame=none,grid=cols,options="header"]
+|===
+|API Endpoints |Class & Javadocs |Paramset
+|v1: `solr/admin/info/health`
+
+v2: `api/node/health` |{solr-javadocs}/solr-core/org/apache/solr/handler/admin/HealthCheckHandler.html[HealthCheckHandler] |`_ADMIN_HEALTH`
+|===
+
+This endpoint can also take the collection or core name in the path (`solr/<collection>/admin/health` or `solr/<core>/admin/health`).
+
 === Analysis Handlers
 
 [horizontal]

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2bd6f246/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java b/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
index ac00bc0..a13181c 100644
--- a/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
+++ b/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
@@ -176,8 +176,8 @@ public interface CommonParams {
   String OMIT_HEADER = "omitHeader";
   String CORES_HANDLER_PATH = "/admin/cores";
   String COLLECTIONS_HANDLER_PATH = "/admin/collections";
-  String HEALTH_CHECK_HANDLER_PATH = "/admin/health";
   String INFO_HANDLER_PATH = "/admin/info";
+  String HEALTH_CHECK_HANDLER_PATH = INFO_HANDLER_PATH + "/health";
   String CONFIGSETS_HANDLER_PATH = "/admin/configs";
   String AUTHZ_PATH = "/admin/authorization";
   String AUTHC_PATH = "/admin/authentication";

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2bd6f246/solr/solrj/src/resources/apispec/node.Info.json
----------------------------------------------------------------------
diff --git a/solr/solrj/src/resources/apispec/node.Info.json b/solr/solrj/src/resources/apispec/node.Info.json
index e7752e6..7d0c2c0 100644
--- a/solr/solrj/src/resources/apispec/node.Info.json
+++ b/solr/solrj/src/resources/apispec/node.Info.json
@@ -1,11 +1,12 @@
 {
-   "description": "Provides information about system properties, threads, logging settings, and system details for a node.",
+   "description": "Provides information about system properties, threads, logging settings, system details and health (available in Solrcloud mode) for a node.",
   "methods": ["GET"],
   "url": {
     "paths": [
       "/node/properties",
       "/node/threads",
       "/node/logging",
-      "/node/system"]
+      "/node/system",
+      "/node/health"]
   }
 }