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

sodonnell pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 3c4683e71b HDDS-9738. Display startTime, pipeline and container counts 
for decommissioning datanode (#6185)
3c4683e71b is described below

commit 3c4683e71ba0de9aa73ed84efade7b5e13a7d14e
Author: Tejaskriya <[email protected]>
AuthorDate: Tue Feb 13 18:53:20 2024 +0530

    HDDS-9738. Display startTime, pipeline and container counts for 
decommissioning datanode (#6185)
---
 .../apache/hadoop/hdds/scm/client/ScmClient.java   |   2 +
 .../protocol/StorageContainerLocationProtocol.java |   2 +
 ...inerLocationProtocolClientSideTranslatorPB.java |  11 ++
 .../src/main/proto/ScmAdminProtocol.proto          |  11 ++
 .../org/apache/hadoop/hdds/scm/FetchMetrics.java   | 220 +++++++++++++++++++++
 ...inerLocationProtocolServerSideTranslatorPB.java |  12 ++
 .../hdds/scm/server/SCMClientProtocolServer.java   |   7 +
 .../hadoop/hdds/scm/node/TestFetchMetrics.java     |  46 +++++
 .../hdds/scm/cli/ContainerOperationClient.java     |   5 +
 .../cli/datanode/DecommissionStatusSubCommand.java |  45 +++++
 .../datanode/TestDecommissionStatusSubCommand.java |  83 +++++---
 11 files changed, 421 insertions(+), 23 deletions(-)

diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
index 402398e36c..fb5a2deee2 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
@@ -452,4 +452,6 @@ public interface ScmClient extends Closeable {
 
   DecommissionScmResponseProto decommissionScm(
       String scmId) throws IOException;
+
+  String getMetrics(String query) throws IOException;
 }
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
index e8bddb42cf..663f317a3b 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
@@ -474,4 +474,6 @@ public interface StorageContainerLocationProtocol extends 
Closeable {
 
   DecommissionScmResponseProto decommissionScm(
       String scmId) throws IOException;
+
+  String getMetrics(String query) throws IOException;
 }
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
index 84a0fa4886..109358c67b 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
@@ -68,6 +68,8 @@ import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolPro
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetPipelineResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerCountRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerCountResponseProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetMetricsRequestProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetMetricsResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.InSafeModeRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ListPipelineRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ListPipelineResponseProto;
@@ -1143,4 +1145,13 @@ public final class 
StorageContainerLocationProtocolClientSideTranslatorPB
                 .getDecommissionScmResponse();
     return response;
   }
+
+  @Override
+  public String getMetrics(String query) throws IOException {
+    GetMetricsRequestProto request = 
GetMetricsRequestProto.newBuilder().setQuery(query).build();
+    GetMetricsResponseProto response = submitRequest(Type.GetMetrics,
+        builder -> 
builder.setGetMetricsRequest(request)).getGetMetricsResponse();
+    String metricsJsonStr = response.getMetricsJson();
+    return metricsJsonStr;
+  }
 }
diff --git a/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto 
b/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
index 6adca817ed..e8b8d62394 100644
--- a/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
+++ b/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
@@ -83,6 +83,7 @@ message ScmContainerLocationRequest {
   optional DecommissionScmRequestProto decommissionScmRequest = 44;
   optional SingleNodeQueryRequestProto singleNodeQueryRequest = 45;
   optional GetContainersOnDecomNodeRequestProto 
getContainersOnDecomNodeRequest = 46;
+  optional GetMetricsRequestProto getMetricsRequest = 47;
 }
 
 message ScmContainerLocationResponse {
@@ -137,6 +138,7 @@ message ScmContainerLocationResponse {
   optional DecommissionScmResponseProto decommissionScmResponse = 44;
   optional SingleNodeQueryResponseProto singleNodeQueryResponse = 45;
   optional GetContainersOnDecomNodeResponseProto 
getContainersOnDecomNodeResponse = 46;
+  optional GetMetricsResponseProto getMetricsResponse = 47;
 
   enum Status {
     OK = 1;
@@ -190,6 +192,7 @@ enum Type {
   DecommissionScm = 40;
   SingleNodeQuery = 41;
   GetContainersOnDecomNode = 42;
+  GetMetrics = 43;
 }
 
 /**
@@ -618,6 +621,14 @@ message GetContainersOnDecomNodeResponseProto {
   repeated ContainersOnDecomNodeProto containersOnDecomNode = 1;
 }
 
+message GetMetricsRequestProto {
+  optional string query = 1;
+}
+
+message GetMetricsResponseProto {
+  optional string metricsJson = 1;
+}
+
 /**
  * Protocol used from an HDFS node to StorageContainerManager.  See the request
  * and response messages for details of the RPC calls.
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/FetchMetrics.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/FetchMetrics.java
new file mode 100644
index 0000000000..0778b9a30d
--- /dev/null
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/FetchMetrics.java
@@ -0,0 +1,220 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.hadoop.hdds.scm;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Array;
+import java.nio.charset.StandardCharsets;
+import java.util.Iterator;
+import java.util.Set;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.IntrospectionException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+import javax.management.ReflectionException;
+import javax.management.RuntimeErrorException;
+import javax.management.RuntimeMBeanException;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.TabularData;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class used to fetch metrics from MBeanServer.
+ */
+public class FetchMetrics {
+  private static final Logger LOG = 
LoggerFactory.getLogger(FetchMetrics.class);
+  private transient MBeanServer mBeanServer;
+  private transient JsonFactory jsonFactory;
+
+  public FetchMetrics() {
+    this.mBeanServer = ManagementFactory.getPlatformMBeanServer();
+    this.jsonFactory = new JsonFactory();
+  }
+
+  public String getMetrics(String qry) {
+    try {
+      JsonGenerator jg = null;
+      ByteArrayOutputStream opStream = new ByteArrayOutputStream();
+
+      try {
+        jg = this.jsonFactory.createGenerator(opStream, JsonEncoding.UTF8);
+        jg.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
+        jg.useDefaultPrettyPrinter();
+        jg.writeStartObject();
+        if (qry == null) {
+          qry = "*:*";
+        }
+        this.listBeans(jg, new ObjectName(qry));
+      } finally {
+        if (jg != null) {
+          jg.close();
+        }
+      }
+      return new String(opStream.toByteArray(), StandardCharsets.UTF_8);
+    } catch (IOException | MalformedObjectNameException ex) {
+      LOG.error("Caught an exception while processing getMetrics request", ex);
+    }
+    return null;
+  }
+
+  private void listBeans(JsonGenerator jg, ObjectName qry)
+      throws IOException {
+    LOG.debug("Listing beans for " + qry);
+    Set<ObjectName> names = null;
+    names = this.mBeanServer.queryNames(qry, (QueryExp) null);
+    jg.writeArrayFieldStart("beans");
+    Iterator<ObjectName> it = names.iterator();
+
+    while (it.hasNext()) {
+      ObjectName oname = (ObjectName) it.next();
+      String code = "";
+
+      MBeanInfo minfo;
+      try {
+        minfo = this.mBeanServer.getMBeanInfo(oname);
+        code = minfo.getClassName();
+        String prs = "";
+
+        try {
+          if ("org.apache.commons.modeler.BaseModelMBean".equals(code)) {
+            prs = "modelerType";
+            code = (String) this.mBeanServer.getAttribute(oname, prs);
+          }
+        } catch (AttributeNotFoundException | MBeanException | 
RuntimeException | ReflectionException ex) {
+          LOG.error("getting attribute " + prs + " of " + oname + " threw an 
exception", ex);
+        }
+      } catch (InstanceNotFoundException var17) {
+        continue;
+      } catch (IntrospectionException | ReflectionException ex) {
+        LOG.error("Problem while trying to process JMX query: " + qry + " with 
MBean " + oname, ex);
+        continue;
+      }
+      jg.writeStartObject();
+      jg.writeStringField("name", oname.toString());
+      jg.writeStringField("modelerType", code);
+      MBeanAttributeInfo[] attrs = minfo.getAttributes();
+      for (int i = 0; i < attrs.length; ++i) {
+        this.writeAttribute(jg, oname, attrs[i]);
+      }
+      jg.writeEndObject();
+    }
+    jg.writeEndArray();
+  }
+
+  private void writeAttribute(JsonGenerator jg, ObjectName oname, 
MBeanAttributeInfo attr) throws IOException {
+    if (attr.isReadable()) {
+      String attName = attr.getName();
+      if (!"modelerType".equals(attName)) {
+        if (attName.indexOf("=") < 0 && attName.indexOf(":") < 0 && 
attName.indexOf(" ") < 0) {
+          Object value = null;
+
+          try {
+            value = this.mBeanServer.getAttribute(oname, attName);
+          } catch (RuntimeMBeanException var7) {
+            if (var7.getCause() instanceof UnsupportedOperationException) {
+              LOG.debug("getting attribute " + attName + " of " + oname + " 
threw an exception", var7);
+            } else {
+              LOG.error("getting attribute " + attName + " of " + oname + " 
threw an exception", var7);
+            }
+            return;
+          } catch (RuntimeErrorException var8) {
+            LOG.error("getting attribute {} of {} threw an exception", new 
Object[]{attName, oname, var8});
+            return;
+          } catch (MBeanException | RuntimeException | ReflectionException ex) 
{
+            LOG.error("getting attribute " + attName + " of " + oname + " 
threw an exception", ex);
+            return;
+          } catch (AttributeNotFoundException | InstanceNotFoundException ex) {
+            return;
+          }
+          this.writeAttribute(jg, attName, value);
+        }
+      }
+    }
+  }
+
+  private void writeAttribute(JsonGenerator jg, String attName, Object value) 
throws IOException {
+    jg.writeFieldName(attName);
+    this.writeObject(jg, value);
+  }
+
+  private void writeObject(JsonGenerator jg, Object value) throws IOException {
+    if (value == null) {
+      jg.writeNull();
+    } else {
+      Class<?> c = value.getClass();
+      Object entry;
+      if (c.isArray()) {
+        jg.writeStartArray();
+        int len = Array.getLength(value);
+
+        for (int j = 0; j < len; ++j) {
+          entry = Array.get(value, j);
+          this.writeObject(jg, entry);
+        }
+
+        jg.writeEndArray();
+      } else if (value instanceof Number) {
+        Number n = (Number) value;
+        jg.writeNumber(n.toString());
+      } else if (value instanceof Boolean) {
+        Boolean b = (Boolean) value;
+        jg.writeBoolean(b);
+      } else if (value instanceof CompositeData) {
+        CompositeData cds = (CompositeData) value;
+        CompositeType comp = cds.getCompositeType();
+        Set<String> keys = comp.keySet();
+        jg.writeStartObject();
+        Iterator var7 = keys.iterator();
+
+        while (var7.hasNext()) {
+          String key = (String) var7.next();
+          this.writeAttribute(jg, key, cds.get(key));
+        }
+
+        jg.writeEndObject();
+      } else if (value instanceof TabularData) {
+        TabularData tds = (TabularData) value;
+        jg.writeStartArray();
+        Iterator var14 = tds.values().iterator();
+
+        while (var14.hasNext()) {
+          entry = var14.next();
+          this.writeObject(jg, entry);
+        }
+        jg.writeEndArray();
+      } else {
+        jg.writeString(value.toString());
+      }
+    }
+  }
+}
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocolServerSideTranslatorPB.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocolServerSideTranslatorPB.java
index f402b9309f..a44536bf44 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocolServerSideTranslatorPB.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocolServerSideTranslatorPB.java
@@ -72,6 +72,8 @@ import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolPro
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetPipelineResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetSafeModeRuleStatusesRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetSafeModeRuleStatusesResponseProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetMetricsRequestProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetMetricsResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.InSafeModeRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.InSafeModeResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.ListPipelineRequestProto;
@@ -714,6 +716,12 @@ public final class 
StorageContainerLocationProtocolServerSideTranslatorPB
               .setDecommissionScmResponse(decommissionScm(
                   request.getDecommissionScmRequest()))
               .build();
+      case GetMetrics:
+        return ScmContainerLocationResponse.newBuilder()
+            .setCmdType(request.getCmdType())
+            .setStatus(Status.OK)
+            .setGetMetricsResponse(getMetrics(request.getGetMetricsRequest()))
+            .build();
       default:
         throw new IllegalArgumentException(
             "Unknown command type: " + request.getCmdType());
@@ -1287,4 +1295,8 @@ public final class 
StorageContainerLocationProtocolServerSideTranslatorPB
     return impl.decommissionScm(
         request.getScmId());
   }
+
+  public GetMetricsResponseProto getMetrics(GetMetricsRequestProto request) 
throws IOException {
+    return 
GetMetricsResponseProto.newBuilder().setMetricsJson(impl.getMetrics(request.getQuery())).build();
+  }
 }
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
index 13bef8590b..faee4fcaaa 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
@@ -63,6 +63,7 @@ import org.apache.hadoop.hdds.scm.ha.SCMHAUtils;
 import org.apache.hadoop.hdds.scm.ha.SCMRatisServer;
 import org.apache.hadoop.hdds.scm.ha.SCMRatisServerImpl;
 import org.apache.hadoop.hdds.scm.node.DatanodeUsageInfo;
+import org.apache.hadoop.hdds.scm.FetchMetrics;
 import org.apache.hadoop.hdds.scm.node.NodeStatus;
 import org.apache.hadoop.hdds.scm.node.states.NodeNotFoundException;
 import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
@@ -1373,4 +1374,10 @@ public class SCMClientProtocolServer implements
     }
     return decommissionScmResponseBuilder.build();
   }
+
+  @Override
+  public String getMetrics(String query) throws IOException {
+    FetchMetrics fetchMetrics = new FetchMetrics();
+    return fetchMetrics.getMetrics(query);
+  }
 }
diff --git 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestFetchMetrics.java
 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestFetchMetrics.java
new file mode 100644
index 0000000000..ede005745e
--- /dev/null
+++ 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestFetchMetrics.java
@@ -0,0 +1,46 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.hadoop.hdds.scm.node;
+
+import org.apache.hadoop.hdds.scm.FetchMetrics;
+import org.junit.jupiter.api.Test;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class TestFetchMetrics {
+  private static FetchMetrics fetchMetrics = new FetchMetrics();
+
+  @Test
+  public void testFetchAll() {
+    String result = fetchMetrics.getMetrics(null);
+    Pattern p = Pattern.compile("beans", Pattern.MULTILINE);
+    Matcher m = p.matcher(result);
+    assertTrue(m.find());
+  }
+
+  @Test
+  public void testFetchFiltered() {
+    String result = 
fetchMetrics.getMetrics("Hadoop:service=StorageContainerManager,name=NodeDecommissionMetrics");
+    Pattern p = Pattern.compile("beans", Pattern.MULTILINE);
+    Matcher m = p.matcher(result);
+    assertTrue(m.find());
+  }
+}
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
index d07e696e7e..6a5550e9fb 100644
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
@@ -563,4 +563,9 @@ public class ContainerOperationClient implements ScmClient {
     return storageContainerLocationClient.decommissionScm(scmId);
   }
 
+  @Override
+  public String getMetrics(String query) throws IOException {
+    return storageContainerLocationClient.getMetrics(query);
+  }
+
 }
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DecommissionStatusSubCommand.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DecommissionStatusSubCommand.java
index b53632f8ee..17d577ff2d 100644
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DecommissionStatusSubCommand.java
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DecommissionStatusSubCommand.java
@@ -17,6 +17,10 @@
  */
 package org.apache.hadoop.hdds.scm.cli.datanode;
 
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Strings;
 import org.apache.hadoop.hdds.cli.HddsVersionProvider;
 import org.apache.hadoop.hdds.protocol.DatanodeDetails;
@@ -27,6 +31,9 @@ import org.apache.hadoop.hdds.scm.container.ContainerID;
 import picocli.CommandLine;
 
 import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -81,17 +88,55 @@ public class DecommissionStatusSubCommand extends 
ScmSubcommand {
           decommissioningNodes.size() + " node(s)");
     }
 
+    String metricsJson = 
scmClient.getMetrics("Hadoop:service=StorageContainerManager,name=NodeDecommissionMetrics");
+    int numDecomNodes = -1;
+    JsonNode jsonNode = null;
+    if (metricsJson != null) {
+      ObjectMapper objectMapper = new ObjectMapper();
+      JsonFactory factory = objectMapper.getFactory();
+      JsonParser parser = factory.createParser(metricsJson);
+      jsonNode = (JsonNode) objectMapper.readTree(parser).get("beans").get(0);
+      JsonNode totalDecom = 
jsonNode.get("DecommissioningMaintenanceNodesTotal");
+      numDecomNodes = (totalDecom == null ? -1 : 
Integer.parseInt(totalDecom.toString()));
+    }
+
     for (HddsProtos.Node node : decommissioningNodes) {
       DatanodeDetails datanode = DatanodeDetails.getFromProtoBuf(
           node.getNodeID());
       printDetails(datanode);
+      printCounts(datanode, jsonNode, numDecomNodes);
       Map<String, List<ContainerID>> containers = 
scmClient.getContainersOnDecomNode(datanode);
       System.out.println(containers);
     }
   }
+
   private void printDetails(DatanodeDetails datanode) {
     System.out.println("\nDatanode: " + datanode.getUuid().toString() +
         " (" + datanode.getNetworkLocation() + "/" + datanode.getIpAddress()
         + "/" + datanode.getHostName() + ")");
   }
+
+  private void printCounts(DatanodeDetails datanode, JsonNode counts, int 
numDecomNodes) {
+    try {
+      for (int i = 1; i <= numDecomNodes; i++) {
+        if (datanode.getHostName().equals(counts.get("tag.datanode." + 
i).asText())) {
+          int pipelines = 
Integer.parseInt(counts.get("PipelinesWaitingToCloseDN." + i).toString());
+          double underReplicated = 
Double.parseDouble(counts.get("UnderReplicatedDN." + i).toString());
+          double unclosed = 
Double.parseDouble(counts.get("UnclosedContainersDN." + i).toString());
+          long startTime = Long.parseLong(counts.get("StartTimeDN." + 
i).toString());
+          System.out.print("Decommission started at : ");
+          Date date = new Date(startTime);
+          DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss z");
+          System.out.println(formatter.format(date));
+          System.out.println("No. of Pipelines: " + pipelines);
+          System.out.println("No. of UnderReplicated containers: " + 
underReplicated);
+          System.out.println("No. of Unclosed Containers: " + unclosed);
+          return;
+        }
+      }
+      System.err.println("Error getting pipeline and container counts for " + 
datanode.getHostName());
+    } catch (NullPointerException ex) {
+      System.err.println("Error getting pipeline and container counts for " + 
datanode.getHostName());
+    }
+  }
 }
diff --git 
a/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDecommissionStatusSubCommand.java
 
b/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDecommissionStatusSubCommand.java
index 41c31caf1f..ad0323d334 100644
--- 
a/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDecommissionStatusSubCommand.java
+++ 
b/hadoop-hdds/tools/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestDecommissionStatusSubCommand.java
@@ -60,6 +60,7 @@ public class TestDecommissionStatusSubCommand {
   private DecommissionStatusSubCommand cmd;
   private List<HddsProtos.Node> nodes = getNodeDetails(2);
   private Map<String, List<ContainerID>> containerOnDecom = 
getContainersOnDecomNodes();
+  private ArrayList<String> metrics = getMetrics();
 
   @BeforeEach
   public void setup() throws UnsupportedEncodingException {
@@ -80,6 +81,7 @@ public class TestDecommissionStatusSubCommand {
     when(scmClient.queryNode(any(), any(), any(), any()))
         .thenAnswer(invocation -> nodes); // 2 nodes decommissioning
     
when(scmClient.getContainersOnDecomNode(any())).thenReturn(containerOnDecom);
+    when(scmClient.getMetrics(any())).thenReturn(metrics.get(1));
 
     cmd.execute(scmClient);
     Pattern p = Pattern.compile("Decommission\\sStatus:\\s" +
@@ -91,15 +93,17 @@ public class TestDecommissionStatusSubCommand {
     p = Pattern.compile("Datanode:\\s.*host0\\)");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertTrue(m.find());
-    p = Pattern.compile("host0.*[\r\n].*UnderReplicated.*UnClosed", 
Pattern.MULTILINE);
+    p = Pattern.compile("Datanode:\\s.*host1\\)");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertTrue(m.find());
-    p = Pattern.compile("Datanode:\\s.*host1\\)");
+    p = Pattern.compile("No\\. of Pipelines:");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertTrue(m.find());
-    p = Pattern.compile("host1.*[\r\n].*UnderReplicated.*UnClosed", 
Pattern.MULTILINE);
+    assertTrue(m.find()); // metrics for both are shown
+    p = Pattern.compile("UnderReplicated=.* UnClosed=");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertTrue(m.find());
+    assertTrue(m.find()); // container lists for both are shown
   }
 
   @Test
@@ -109,6 +113,7 @@ public class TestDecommissionStatusSubCommand {
     when(scmClient.queryNode(any(), any(), any(), any()))
         .thenReturn(new ArrayList<>());
     when(scmClient.getContainersOnDecomNode(any())).thenReturn(new 
HashMap<>());
+    when(scmClient.getMetrics(any())).thenReturn(metrics.get(0));
     cmd.execute(scmClient);
 
     Pattern p = Pattern.compile("Decommission\\sStatus:\\s" +
@@ -117,10 +122,10 @@ public class TestDecommissionStatusSubCommand {
     assertTrue(m.find());
 
     // no host details are shown
-    p = Pattern.compile("Datanode:\\s.*host0\\)", Pattern.MULTILINE);
+    p = Pattern.compile("Datanode:\\s.*host0\\)");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertFalse(m.find());
-    p = Pattern.compile("Datanode:\\s.*host1.\\)", Pattern.MULTILINE);
+    p = Pattern.compile("Datanode:\\s.*host1.\\)");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertFalse(m.find());
   }
@@ -131,24 +136,22 @@ public class TestDecommissionStatusSubCommand {
     when(scmClient.queryNode(any(), any(), any(), any()))
         .thenAnswer(invocation -> nodes); // 2 nodes decommissioning
     
when(scmClient.getContainersOnDecomNode(any())).thenReturn(containerOnDecom);
+    when(scmClient.getMetrics(any())).thenReturn(metrics.get(1));
 
     CommandLine c = new CommandLine(cmd);
     c.parseArgs("--id", nodes.get(0).getNodeID().getUuid());
     cmd.execute(scmClient); // check status of host0
 
-    Pattern p = Pattern.compile("Datanode:\\s.*host0\\)", Pattern.MULTILINE);
+    Pattern p = Pattern.compile("Datanode:\\s.*host0\\)");
     Matcher m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertTrue(m.find());
-    p = Pattern.compile("host0.*[\r\n].*UnderReplicated.*UnClosed", 
Pattern.MULTILINE);
-    m = p.matcher(outContent.toString(DEFAULT_ENCODING));
-    assertTrue(m.find());
-
     // as uuid of only host0 is passed, host1 should NOT be displayed
-    p = Pattern.compile("Datanode:\\s.*host1.\\)", Pattern.MULTILINE);
+    p = Pattern.compile("Datanode:\\s.*host1.\\)");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertFalse(m.find());
-    p = Pattern.compile("host1.*[\r\n].*UnderReplicated.*UnClosed", 
Pattern.MULTILINE);
+    p = Pattern.compile("UnderReplicated=.*UnClosed=");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
+    assertTrue(m.find());
     assertFalse(m.find());
   }
 
@@ -161,6 +164,7 @@ public class TestDecommissionStatusSubCommand {
         .thenReturn(containerOnDecom);
     
when(scmClient.getContainersOnDecomNode(DatanodeDetails.getFromProtoBuf(nodes.get(1).getNodeID())))
         .thenReturn(new HashMap<>());
+    when(scmClient.getMetrics(any())).thenReturn(metrics.get(2));
 
     CommandLine c = new CommandLine(cmd);
     c.parseArgs("--id", nodes.get(1).getNodeID().getUuid());
@@ -172,10 +176,10 @@ public class TestDecommissionStatusSubCommand {
     assertTrue(m.find());
 
     // no host details are shown
-    p = Pattern.compile("Datanode:\\s.*host0\\)", Pattern.MULTILINE);
+    p = Pattern.compile("Datanode:\\s.*host0\\)");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertFalse(m.find());
-    p = Pattern.compile("Datanode:\\s.*host1\\)", Pattern.MULTILINE);
+    p = Pattern.compile("Datanode:\\s.*host1\\)");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertFalse(m.find());
   }
@@ -186,24 +190,22 @@ public class TestDecommissionStatusSubCommand {
     when(scmClient.queryNode(any(), any(), any(), any()))
         .thenAnswer(invocation -> nodes); // 2 nodes decommissioning
     
when(scmClient.getContainersOnDecomNode(any())).thenReturn(containerOnDecom);
+    when(scmClient.getMetrics(any())).thenReturn(metrics.get(1));
 
     CommandLine c = new CommandLine(cmd);
     c.parseArgs("--ip", nodes.get(1).getNodeID().getIpAddress());
     cmd.execute(scmClient); // check status of host1
 
-    Pattern p = Pattern.compile("Datanode:\\s.*host1\\)", Pattern.MULTILINE);
+    Pattern p = Pattern.compile("Datanode:\\s.*host1\\)");
     Matcher m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertTrue(m.find());
-    p = Pattern.compile("host1.*[\r\n].*UnderReplicated.*UnClosed", 
Pattern.MULTILINE);
-    m = p.matcher(outContent.toString(DEFAULT_ENCODING));
-    assertTrue(m.find());
-
     // as IpAddress of only host1 is passed, host0 should NOT be displayed
-    p = Pattern.compile("Datanode:\\s.*host0.\\)", Pattern.MULTILINE);
+    p = Pattern.compile("Datanode:\\s.*host0.\\)");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertFalse(m.find());
-    p = Pattern.compile("host0.*[\r\n].*UnderReplicated.*UnClosed", 
Pattern.MULTILINE);
+    p = Pattern.compile("UnderReplicated=.*UnClosed=");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
+    assertTrue(m.find());
     assertFalse(m.find());
   }
 
@@ -216,6 +218,7 @@ public class TestDecommissionStatusSubCommand {
         .thenReturn(containerOnDecom);
     
when(scmClient.getContainersOnDecomNode(DatanodeDetails.getFromProtoBuf(nodes.get(1).getNodeID())))
         .thenReturn(new HashMap<>());
+    when(scmClient.getMetrics(any())).thenReturn(metrics.get(2));
 
     CommandLine c = new CommandLine(cmd);
     c.parseArgs("--ip", nodes.get(1).getNodeID().getIpAddress());
@@ -226,11 +229,11 @@ public class TestDecommissionStatusSubCommand {
     Matcher m = p.matcher(errContent.toString(DEFAULT_ENCODING));
     assertTrue(m.find());
 
-    p = Pattern.compile("Datanode:\\s.*host0\\)", Pattern.MULTILINE);
+    p = Pattern.compile("Datanode:\\s.*host0\\)");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertFalse(m.find());
 
-    p = Pattern.compile("Datanode:\\s.*host1\\)", Pattern.MULTILINE);
+    p = Pattern.compile("Datanode:\\s.*host1\\)");
     m = p.matcher(outContent.toString(DEFAULT_ENCODING));
     assertFalse(m.find());
   }
@@ -275,4 +278,38 @@ public class TestDecommissionStatusSubCommand {
     return containerMap;
   }
 
+  private ArrayList<String> getMetrics() {
+    ArrayList<String> result = new ArrayList<>();
+    // no nodes decommissioning
+    result.add("{  \"beans\" : [ {    " +
+        "\"name\" : 
\"Hadoop:service=StorageContainerManager,name=NodeDecommissionMetrics\",    " +
+        "\"modelerType\" : \"NodeDecommissionMetrics\",    
\"DecommissioningMaintenanceNodesTotal\" : 0,    " +
+        "\"RecommissionNodesTotal\" : 0,    \"PipelinesWaitingToCloseTotal\" : 
0,    " +
+        "\"ContainersUnderReplicatedTotal\" : 0,    
\"ContainersUnClosedTotal\" : 0,    " +
+        "\"ContainersSufficientlyReplicatedTotal\" : 0  } ]}");
+    // 2 nodes in decommisioning
+    result.add("{  \"beans\" : [ {    " +
+        "\"name\" : 
\"Hadoop:service=StorageContainerManager,name=NodeDecommissionMetrics\",    " +
+        "\"modelerType\" : \"NodeDecommissionMetrics\",    
\"DecommissioningMaintenanceNodesTotal\" : 2,    " +
+        "\"RecommissionNodesTotal\" : 0,    \"PipelinesWaitingToCloseTotal\" : 
2,    " +
+        "\"ContainersUnderReplicatedTotal\" : 6,    
\"ContainersUnclosedTotal\" : 6,    " +
+        "\"ContainersSufficientlyReplicatedTotal\" : 10,   " +
+        "\"tag.datanode.1\" : \"host0\",    \"tag.Hostname.1\" : \"host0\",    
" +
+        "\"PipelinesWaitingToCloseDN.1\" : 1,    \"UnderReplicatedDN.1\" : 3,  
  " +
+        "\"SufficientlyReplicatedDN.1\" : 0,    \"UnclosedContainersDN.1\" : 
3,    \"StartTimeDN.1\" : 111211,    " +
+        "\"tag.datanode.2\" : \"host1\",    \"tag.Hostname.2\" : \"host1\",    
" +
+        "\"PipelinesWaitingToCloseDN.2\" : 1,    \"UnderReplicatedDN.2\" : 3,  
  " +
+        "\"SufficientlyReplicatedDN.2\" : 0,    \"UnclosedContainersDN.2\" : 
3,    \"StartTimeDN.2\" : 221221} ]}");
+    // only host 1 decommissioning
+    result.add("{  \"beans\" : [ {    " +
+        "\"name\" : 
\"Hadoop:service=StorageContainerManager,name=NodeDecommissionMetrics\",    " +
+        "\"modelerType\" : \"NodeDecommissionMetrics\",    
\"DecommissioningMaintenanceNodesTotal\" : 1,    " +
+        "\"RecommissionNodesTotal\" : 0,    \"PipelinesWaitingToCloseTotal\" : 
1,    " +
+        "\"ContainersUnderReplicatedTotal\" : 3,    
\"ContainersUnclosedTotal\" : 3,    " +
+        "\"ContainersSufficientlyReplicatedTotal\" : 10,   " +
+        "\"tag.datanode.1\" : \"host0\",\n    \"tag.Hostname.1\" : 
\"host0\",\n    " +
+        "\"PipelinesWaitingToCloseDN.1\" : 1,\n    \"UnderReplicatedDN.1\" : 
3,\n    " +
+        "\"SufficientlyReplicatedDN.1\" : 0,\n    \"UnclosedContainersDN.1\" : 
3,    \"StartTimeDN.1\" : 221221} ]}");
+    return result;
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to