[ASTERIXDB-2142][*DB][API] Guard against unavailable nc detail

hcc.getNodeDetailsJSON() & hcc.getThreadDump() APIs returns the null
string in case of unknown or not connected node- handle these in the
HTTP API.

Change-Id: I06a00f191812d25a6fef6c7cae7d693b258a6b6d
Reviewed-on: https://asterix-gerrit.ics.uci.edu/2096
Sonar-Qube: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Contrib: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Murtadha Hubail <mhubail@apache.org>
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ClusterControllerDetailsApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ClusterControllerDetailsApiServlet.java
index 1faa316..dcd43cb 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ClusterControllerDetailsApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ClusterControllerDetailsApiServlet.java
@@ -81,9 +81,9 @@
         } else if (parts.length == 1) {
             switch (parts[0]) {
                 case "config":
-                    return OBJECT_MAPPER.readValue(hcc.getNodeDetailsJSON(null, false, true), ObjectNode.class);
+                    return OBJECT_MAPPER.readValue(processNodeDetails(hcc, false, true), ObjectNode.class);
                 case "stats":
-                    return OBJECT_MAPPER.readValue(hcc.getNodeDetailsJSON(null, true, false), ObjectNode.class);
+                    return OBJECT_MAPPER.readValue(processNodeDetails(hcc, true, false), ObjectNode.class);
                 case "threaddump":
                     return processCCThreadDump(hcc);
 
@@ -96,10 +96,19 @@
         }
     }
 
+    private String processNodeDetails(IHyracksClientConnection hcc, boolean includeStats, boolean includeConfig)
+            throws Exception {
+        final String details = hcc.getNodeDetailsJSON(null, includeStats, includeConfig);
+        if (details == null) {
+            throw new IllegalStateException("unable to retrieve details for CC");
+        }
+        return details;
+    }
+
     private ObjectNode processCCThreadDump(IHyracksClientConnection hcc) throws Exception {
         String dump = hcc.getThreadDump(null);
         if (dump == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalStateException("unable to retrieve thread dump for CC");
         }
         return (ObjectNode) OBJECT_MAPPER.readTree(dump);
     }
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/DiagnosticsApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/DiagnosticsApiServlet.java
index eafda09..a79b137 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/DiagnosticsApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/DiagnosticsApiServlet.java
@@ -106,9 +106,9 @@
         Map<String, Future<JsonNode>> ncData;
         ncData = new HashMap<>();
         ncData.put("threaddump",
-                executor.submit(() -> fixupKeys((ObjectNode) OBJECT_MAPPER.readTree(hcc.getThreadDump(nc)))));
+                executor.submit(() -> fixupKeys((ObjectNode) OBJECT_MAPPER.readTree(processThreadDump(nc)))));
         ncData.put("config", executor
-                .submit(() -> fixupKeys((ObjectNode) OBJECT_MAPPER.readTree(hcc.getNodeDetailsJSON(nc, false, true)))));
+                .submit(() -> fixupKeys((ObjectNode) OBJECT_MAPPER.readTree(processNodeDetails(nc, false, true)))));
         ncData.put("stats", executor.submit(() -> fixupKeys(processNodeStats(hcc, nc))));
         return ncData;
     }
@@ -117,11 +117,11 @@
         Map<String, Future<JsonNode>> ccFutureData;
         ccFutureData = new HashMap<>();
         ccFutureData.put("threaddump",
-                executor.submit(() -> fixupKeys((ObjectNode) OBJECT_MAPPER.readTree(hcc.getThreadDump(null)))));
+                executor.submit(() -> fixupKeys((ObjectNode) OBJECT_MAPPER.readTree(processThreadDump(null)))));
         ccFutureData.put("config", executor.submit(
-                () -> fixupKeys((ObjectNode) OBJECT_MAPPER.readTree(hcc.getNodeDetailsJSON(null, false, true)))));
+                () -> fixupKeys((ObjectNode) OBJECT_MAPPER.readTree(processNodeDetails(null, false, true)))));
         ccFutureData.put("stats", executor.submit(
-                () -> fixupKeys((ObjectNode) OBJECT_MAPPER.readTree(hcc.getNodeDetailsJSON(null, true, false)))));
+                () -> fixupKeys((ObjectNode) OBJECT_MAPPER.readTree(processNodeDetails(null, true, false)))));
         return ccFutureData;
     }
 
@@ -143,4 +143,13 @@
             }
         }
     }
+
+    protected String processNodeDetails(String node, boolean includeStats, boolean includeConfig) throws Exception {
+        return checkNullDetail(node, hcc.getNodeDetailsJSON(node, includeStats, includeConfig));
+    }
+
+    protected String processThreadDump(String node) throws Exception {
+        return checkNullDetail(node, hcc.getThreadDump(node));
+    }
+
 }
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NodeControllerDetailsApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NodeControllerDetailsApiServlet.java
index 2c94f86..f443d09 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NodeControllerDetailsApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NodeControllerDetailsApiServlet.java
@@ -40,6 +40,7 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+
 import io.netty.handler.codec.http.HttpResponseStatus;
 
 public class NodeControllerDetailsApiServlet extends ClusterApiServlet {
@@ -139,10 +140,7 @@
     }
 
     protected ObjectNode processNodeStats(IHyracksClientConnection hcc, String node) throws Exception {
-        final String details = hcc.getNodeDetailsJSON(node, true, false);
-        if (details == null) {
-            throw new IllegalArgumentException();
-        }
+        final String details = checkNullDetail(node, hcc.getNodeDetailsJSON(node, true, false));
         ObjectNode json = (ObjectNode) OBJECT_MAPPER.readTree(details);
         int index = json.get("rrd-ptr").asInt() - 1;
         json.remove("rrd-ptr");
@@ -189,10 +187,7 @@
     }
 
     private ObjectNode processNodeConfig(IHyracksClientConnection hcc, String node) throws Exception {
-        String config = hcc.getNodeDetailsJSON(node, false, true);
-        if (config == null) {
-            throw new IllegalArgumentException();
-        }
+        String config = checkNullDetail(node, hcc.getNodeDetailsJSON(node, false, true));
         return (ObjectNode) OBJECT_MAPPER.readTree(config);
     }
 
@@ -200,13 +195,22 @@
         if ("cc".equals(node)) {
             return OBJECT_MAPPER.createObjectNode();
         }
-        String dump = hcc.getThreadDump(node);
-        if (dump == null) {
-            // check to see if this is a node that is simply down
-            IClusterStateManager csm = appCtx.getClusterStateManager();
-            ClusterPartition[] cp = csm.getNodePartitions(node);
-            throw cp != null ? new IllegalStateException() : new IllegalArgumentException();
-        }
+        String dump = checkNullDetail(node, hcc.getThreadDump(node));
         return (ObjectNode) OBJECT_MAPPER.readTree(dump);
     }
+
+    protected String checkNullDetail(String node, String value) {
+        if (value != null) {
+            return value;
+        }
+        if (node == null) {
+            // something is seriously wrong if we can't get the cc detail
+            throw new IllegalStateException("unable to obtain detail from cc");
+        }
+        // check to see if this is a node that is simply down
+        IClusterStateManager csm = appCtx.getClusterStateManager();
+        ClusterPartition[] cp = csm.getNodePartitions(node);
+        throw cp != null ? new IllegalStateException("unable to obtain detail from node " + node)
+                : new IllegalArgumentException("unknown node " + node);
+    }
 }
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ShutdownApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ShutdownApiServlet.java
index ddd0f1f..ac31e24 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ShutdownApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ShutdownApiServlet.java
@@ -86,10 +86,15 @@
             for (int i = 0; i < ncs.size(); i++) {
                 ObjectNode nc = (ObjectNode) ncs.get(i);
                 String node = nc.get(NODE_ID_KEY).asText();
-                ObjectNode details = (ObjectNode) OBJECT_MAPPER.readTree(hcc.getNodeDetailsJSON(node, false, true));
-                nc.set(PID, details.get(PID));
-                if (details.has(INI) && details.get(INI).has(NCSERVICE_PID)) {
-                    nc.put(NCSERVICE_PID, details.get(INI).get(NCSERVICE_PID).asInt());
+                final String detailsString = hcc.getNodeDetailsJSON(node, false, true);
+                if (detailsString != null) {
+                    ObjectNode details = (ObjectNode) OBJECT_MAPPER.readTree(detailsString);
+                    nc.set(PID, details.get(PID));
+                    if (details.has(INI) && details.get(INI).has(NCSERVICE_PID)) {
+                        nc.put(NCSERVICE_PID, details.get(INI).get(NCSERVICE_PID).asInt());
+                    }
+                } else {
+                    LOGGER.warning("Unable to get node details for " + node + " from hcc");
                 }
             }
             jsonObject.set("cluster", clusterState);
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/client/IHyracksClientConnection.java b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/client/IHyracksClientConnection.java
index 0189135..a7c1d75 100644
--- a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/client/IHyracksClientConnection.java
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/client/IHyracksClientConnection.java
@@ -211,7 +211,7 @@
      *            id the subject node
      * @param includeStats
      * @param includeConfig
-     * @return serialized JSON containing the node details
+     * @return serialized JSON containing the node details, or null if the details are not available (e.g. NC down)
      * @throws Exception
      */
     String getNodeDetailsJSON(String nodeId, boolean includeStats, boolean includeConfig) throws Exception;