diff --git a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DatanodeCommands.java b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DatanodeCommands.java index 8cb2114f57db..1f19279c91f5 100644 --- a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DatanodeCommands.java +++ b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/DatanodeCommands.java @@ -17,10 +17,12 @@ */ package org.apache.hadoop.hdds.scm.cli.datanode; +import com.google.common.annotations.VisibleForTesting; import org.apache.hadoop.hdds.cli.GenericCli; import org.apache.hadoop.hdds.cli.HddsVersionProvider; import org.apache.hadoop.hdds.cli.OzoneAdmin; import org.apache.hadoop.hdds.cli.SubcommandWithParent; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.kohsuke.MetaInfServices; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; @@ -46,6 +48,9 @@ @MetaInfServices(SubcommandWithParent.class) public class DatanodeCommands implements Callable, SubcommandWithParent { + @CommandLine.ParentCommand + private OzoneAdmin parent; + @Spec private CommandSpec spec; @@ -55,6 +60,15 @@ public Void call() throws Exception { return null; } + public OzoneAdmin getParent() { + return parent; + } + + @VisibleForTesting + public void setParent(OzoneConfiguration conf) { + parent = new OzoneAdmin(conf); + } + @Override public Class getParentType() { return OzoneAdmin.class; 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 b53632f8eec5..65be4c493fca 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,22 +17,49 @@ */ package org.apache.hadoop.hdds.scm.cli.datanode; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; +import com.google.gson.Gson; +import org.apache.commons.io.IOUtils; import org.apache.hadoop.hdds.cli.HddsVersionProvider; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; import org.apache.hadoop.hdds.scm.cli.ScmSubcommand; import org.apache.hadoop.hdds.scm.client.ScmClient; import org.apache.hadoop.hdds.scm.container.ContainerID; +import org.apache.hadoop.hdds.server.http.HttpConfig; +import org.apache.hadoop.hdfs.web.URLConnectionFactory; import picocli.CommandLine; +import javax.net.ssl.HttpsURLConnection; import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.net.HttpURLConnection.HTTP_CREATED; +import static java.net.HttpURLConnection.HTTP_OK; import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeOperationalState.DECOMMISSIONING; +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_HTTPS_ADDRESS_KEY; +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_HTTP_BIND_HOST_DEFAULT; +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_HTTPS_BIND_PORT_DEFAULT; +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_HTTP_ADDRESS_KEY; +import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_HTTP_BIND_PORT_DEFAULT; +import static org.apache.hadoop.hdds.server.http.HttpConfig.getHttpPolicy; +import static org.apache.hadoop.hdds.server.http.HttpServer2.HTTPS_SCHEME; +import static org.apache.hadoop.hdds.server.http.HttpServer2.HTTP_SCHEME; /** * Handler to print decommissioning nodes status. @@ -45,6 +72,9 @@ public class DecommissionStatusSubCommand extends ScmSubcommand { + @CommandLine.ParentCommand + private StatusSubCommand parent; + @CommandLine.Option(names = { "--id" }, description = "Show info by datanode UUID", defaultValue = "") @@ -81,10 +111,19 @@ public void execute(ScmClient scmClient) throws IOException { decommissioningNodes.size() + " node(s)"); } + Map counts = getCounts(); + int numDecomNodes; + Double num = (Double) counts.get("DecommissioningMaintenanceNodesTotal"); + if (num == null) { + numDecomNodes = -1; + } else { + numDecomNodes = num.intValue(); + } for (HddsProtos.Node node : decommissioningNodes) { DatanodeDetails datanode = DatanodeDetails.getFromProtoBuf( node.getNodeID()); printDetails(datanode); + printCounts(datanode, counts, numDecomNodes); Map> containers = scmClient.getContainersOnDecomNode(datanode); System.out.println(containers); } @@ -94,4 +133,95 @@ private void printDetails(DatanodeDetails datanode) { " (" + datanode.getNetworkLocation() + "/" + datanode.getIpAddress() + "/" + datanode.getHostName() + ")"); } + private void printCounts(DatanodeDetails datanode, Map counts, int numDecomNodes) { + try { + for (int i = 1; i <= numDecomNodes; i++) { + if (datanode.getHostName().equals(counts.get("tag.datanode." + i))) { + int pipelines = ((Double)counts.get("PipelinesWaitingToCloseDN." + i)).intValue(); + int underReplicated = ((Double)counts.get("UnderReplicatedDN." + i)).intValue(); + int unclosed = ((Double)counts.get("UnclosedContainersDN." + i)).intValue(); + long startTime = ((Double)counts.get("StartTimeDN." + i)).longValue(); + 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()); + } + } + + private Map getCounts() { + Map finalResult = new HashMap<>(); + try { + StringBuffer url = new StringBuffer(); + final OzoneConfiguration ozoneConf = parent + .getParent() + .getParent() + .getOzoneConf(); + final String protocol; + final URLConnectionFactory connectionFactory = URLConnectionFactory.newDefaultURLConnectionFactory(ozoneConf); + final HttpConfig.Policy webPolicy = getHttpPolicy(ozoneConf); + String host; + InputStream inputStream; + int errorCode; + + if (webPolicy.isHttpsEnabled()) { + protocol = HTTPS_SCHEME; + host = ozoneConf.get(OZONE_SCM_HTTPS_ADDRESS_KEY, + OZONE_SCM_HTTP_BIND_HOST_DEFAULT + ":" + OZONE_SCM_HTTPS_BIND_PORT_DEFAULT); + url.append(protocol).append("://").append(host).append("/jmx") + .append("?qry=Hadoop:service=StorageContainerManager,name=NodeDecommissionMetrics"); + + HttpsURLConnection httpsURLConnection = (HttpsURLConnection) connectionFactory + .openConnection(new URL(url.toString())); + httpsURLConnection.connect(); + errorCode = httpsURLConnection.getResponseCode(); + inputStream = httpsURLConnection.getInputStream(); + } else { + protocol = HTTP_SCHEME; + host = ozoneConf.get(OZONE_SCM_HTTP_ADDRESS_KEY, + OZONE_SCM_HTTP_BIND_HOST_DEFAULT + ":" + OZONE_SCM_HTTP_BIND_PORT_DEFAULT); + url.append(protocol + "://" + host).append("/jmx") + .append("?qry=Hadoop:service=StorageContainerManager,name=NodeDecommissionMetrics"); + + HttpURLConnection httpURLConnection = (HttpURLConnection) connectionFactory + .openConnection(new URL(url.toString())); + httpURLConnection.connect(); + errorCode = httpURLConnection.getResponseCode(); + inputStream = httpURLConnection.getInputStream(); + } + + if ((errorCode == HTTP_OK) || (errorCode == HTTP_CREATED)) { + String response = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + HashMap> result = new Gson().fromJson(response, HashMap.class); + finalResult = result.get("beans").get(0); + return finalResult; + } else { + throw new IOException("Unable to retrieve pipeline and container counts."); + } + } catch (MalformedURLException ex) { + System.err.println("Unable to retrieve pipeline and container counts."); + return finalResult; + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + public StatusSubCommand getParent() { + return parent; + } + + @VisibleForTesting + public void setParent(OzoneConfiguration conf) { + parent = new StatusSubCommand(); + parent.setParent(conf); + } + } diff --git a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/StatusSubCommand.java b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/StatusSubCommand.java index 9edcd3425a0d..78b28752622d 100644 --- a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/StatusSubCommand.java +++ b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/StatusSubCommand.java @@ -17,6 +17,8 @@ * limitations under the License. */ +import com.google.common.annotations.VisibleForTesting; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.cli.GenericCli; import org.apache.hadoop.hdds.cli.HddsVersionProvider; import org.apache.hadoop.hdds.cli.SubcommandWithParent; @@ -40,6 +42,9 @@ @MetaInfServices(SubcommandWithParent.class) public class StatusSubCommand implements Callable, SubcommandWithParent { + @CommandLine.ParentCommand + private DatanodeCommands parent; + @CommandLine.Spec private CommandLine.Model.CommandSpec spec; @@ -49,6 +54,16 @@ public Void call() throws Exception { return null; } + public DatanodeCommands getParent() { + return parent; + } + + @VisibleForTesting + public void setParent(OzoneConfiguration conf) { + parent = new DatanodeCommands(); + parent.setParent(conf); + } + @Override public Class getParentType() { return DatanodeCommands.class; 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 ed05d6f0e9d1..e91f48ca5e45 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 @@ -17,10 +17,19 @@ */ package org.apache.hadoop.hdds.scm.cli.datanode; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; import org.apache.hadoop.hdds.scm.client.ScmClient; +import org.apache.hadoop.hdds.scm.ScmConfigKeys; import org.apache.hadoop.hdds.scm.container.ContainerID; +import org.apache.hadoop.http.HttpConfig; +import org.apache.hadoop.ozone.OzoneConfigKeys; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,6 +38,8 @@ import java.io.IOException; import java.io.PrintStream; import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; @@ -60,12 +71,44 @@ public class TestDecommissionStatusSubCommand { private DecommissionStatusSubCommand cmd; private List nodes = getNodeDetails(2); private Map> containerOnDecom = getContainersOnDecomNodes(); + private static HttpServer httpServer; + private OzoneConfiguration conf = new OzoneConfiguration(); + + @BeforeAll + public static void setupScmHttp() throws Exception { + httpServer = HttpServer.create(new InetSocketAddress(15000), 0); + httpServer.createContext("/jmx", new HttpHandler() { + public void handle(HttpExchange exchange) throws IOException { + byte[] response = ("{ \"beans\" : [ { " + + "\"name\" : \"Hadoop:service=StorageContainerManager,name=NodeDecommissionMetrics\", " + + "\"modelerType\" : \"NodeDecommissionMetrics\", \"DecommissioningMaintenanceNodesTotal\" : 0, " + + "\"RecommissionNodesTotal\" : 0, \"PipelinesWaitingToCloseTotal\" : 0, " + + "\"ContainersUnderReplicatedTotal\" : 0, \"ContainersUnClosedTotal\" : 0, " + + "\"ContainersSufficientlyReplicatedTotal\" : 0 } ]}").getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.length); + exchange.getResponseBody().write(response); + exchange.close(); + } + }); + httpServer.start(); + } + @AfterAll + public static void shutdownScmHttp() { + if (httpServer != null) { + httpServer.stop(0); + } + } @BeforeEach public void setup() throws UnsupportedEncodingException { cmd = new DecommissionStatusSubCommand(); System.setOut(new PrintStream(outContent, false, DEFAULT_ENCODING)); System.setErr(new PrintStream(errContent, false, DEFAULT_ENCODING)); + HttpConfig.Policy policy = HttpConfig.Policy.HTTP_ONLY; + conf.set(OzoneConfigKeys.OZONE_HTTP_POLICY_KEY, policy.name()); + conf.set(ScmConfigKeys.OZONE_SCM_HTTP_ADDRESS_KEY, "localhost:15000"); + conf.set(ScmConfigKeys.OZONE_SCM_HTTP_BIND_HOST_KEY, "localhost"); + cmd.setParent(conf); } @AfterEach