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

tejaskriya 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 e0b804ef996 HDDS-13095. Support sorting by most/least used nodes in 
ozone admin datanode list (#8520)
e0b804ef996 is described below

commit e0b804ef996f7ab3af726c0af9e4d385f4195848
Author: sreejasahithi <[email protected]>
AuthorDate: Fri Jul 11 10:16:35 2025 +0530

    HDDS-13095. Support sorting by most/least used nodes in ozone admin 
datanode list (#8520)
---
 .../hdds/scm/cli/datanode/ListInfoSubcommand.java  |  85 ++++++++++++++++
 .../scm/cli/datanode/TestListInfoSubcommand.java   | 108 +++++++++++++++++++++
 2 files changed, 193 insertions(+)

diff --git 
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/ListInfoSubcommand.java
 
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/ListInfoSubcommand.java
index 2b7813f4616..56c50456210 100644
--- 
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/ListInfoSubcommand.java
+++ 
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/ListInfoSubcommand.java
@@ -17,10 +17,12 @@
 
 package org.apache.hadoop.hdds.scm.cli.datanode;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.google.common.base.Strings;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.UUID;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -63,6 +65,9 @@ public class ListInfoSubcommand extends ScmSubcommand {
        defaultValue = "false")
   private boolean json;
 
+  @CommandLine.ArgGroup(exclusive = true, multiplicity = "0..1")
+  private UsageSortingOptions usageSortingOptions;
+
   @CommandLine.Mixin
   private ListLimitOptions listLimitOptions;
 
@@ -71,6 +76,16 @@ public class ListInfoSubcommand extends ScmSubcommand {
 
   private List<Pipeline> pipelines;
 
+  static class UsageSortingOptions {
+    @CommandLine.Option(names = {"--most-used"},
+        description = "Show datanodes sorted by Utilization (most to least).")
+    private boolean mostUsed;
+
+    @CommandLine.Option(names = {"--least-used"},
+        description = "Show datanodes sorted by Utilization (least to most).")
+    private boolean leastUsed;
+  }
+
   @Override
   public void execute(ScmClient scmClient) throws IOException {
     pipelines = scmClient.listPipelines();
@@ -123,6 +138,38 @@ public void execute(ScmClient scmClient) throws 
IOException {
 
   private List<DatanodeWithAttributes> getAllNodes(ScmClient scmClient)
       throws IOException {
+
+    // If sorting is requested
+    if (usageSortingOptions != null && (usageSortingOptions.mostUsed || 
usageSortingOptions.leastUsed)) {
+      boolean sortByMostUsed = usageSortingOptions.mostUsed;
+      List<HddsProtos.DatanodeUsageInfoProto> usageInfos = 
scmClient.getDatanodeUsageInfo(sortByMostUsed, 
+          Integer.MAX_VALUE);
+
+      return usageInfos.stream()
+          .map(p -> {
+            String uuidStr = p.getNode().getUuid();
+            UUID parsedUuid = UUID.fromString(uuidStr);
+
+            try {
+              HddsProtos.Node node = scmClient.queryNode(parsedUuid);
+              long capacity = p.getCapacity();
+              long used = capacity - p.getRemaining();
+              double percentUsed = (capacity > 0) ? (used * 100.0) / capacity 
: 0.0;
+              return new DatanodeWithAttributes(
+                  DatanodeDetails.getFromProtoBuf(node.getNodeID()),
+                  node.getNodeOperationalStates(0),
+                  node.getNodeStates(0),
+                  used,
+                  capacity,
+                  percentUsed);
+            } catch (IOException e) {
+              return null;
+            }
+          })
+          .filter(Objects::nonNull)
+          .collect(Collectors.toList());
+    }
+    
     List<HddsProtos.Node> nodes = scmClient.queryNode(null,
         null, HddsProtos.QueryScope.CLUSTER, "");
 
@@ -165,12 +212,24 @@ private void printDatanodeInfo(DatanodeWithAttributes 
dna) {
     System.out.println("Operational State: " + dna.getOpState());
     System.out.println("Health State: " + dna.getHealthState());
     System.out.println("Related pipelines:\n" + pipelineListInfo);
+
+    if (dna.getUsed() != null && dna.getCapacity() != null && dna.getUsed() >= 
0 && dna.getCapacity() > 0) {
+      System.out.println("Capacity: " + dna.getCapacity());
+      System.out.println("Used: " + dna.getUsed());
+      System.out.printf("Percentage Used : %.2f%%%n%n", dna.getPercentUsed());
+    }
   }
 
   private static class DatanodeWithAttributes {
     private DatanodeDetails datanodeDetails;
     private HddsProtos.NodeOperationalState operationalState;
     private HddsProtos.NodeState healthState;
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private Long used = null;
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private Long capacity = null;
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private Double percentUsed = null;
 
     DatanodeWithAttributes(DatanodeDetails dn,
         HddsProtos.NodeOperationalState opState,
@@ -180,6 +239,20 @@ private static class DatanodeWithAttributes {
       this.healthState = healthState;
     }
 
+    DatanodeWithAttributes(DatanodeDetails dn,
+                           HddsProtos.NodeOperationalState opState,
+                           HddsProtos.NodeState healthState,
+                           long used,
+                           long capacity,
+                           double percentUsed) {
+      this.datanodeDetails = dn;
+      this.operationalState = opState;
+      this.healthState = healthState;
+      this.used = used;
+      this.capacity = capacity;
+      this.percentUsed = percentUsed;
+    }
+
     public DatanodeDetails getDatanodeDetails() {
       return datanodeDetails;
     }
@@ -191,5 +264,17 @@ public HddsProtos.NodeOperationalState getOpState() {
     public HddsProtos.NodeState getHealthState() {
       return healthState;
     }
+
+    public Long getUsed() {
+      return used;
+    }
+    
+    public Long getCapacity() {
+      return capacity;
+    }
+    
+    public Double getPercentUsed() {
+      return percentUsed;
+    }
   }
 }
diff --git 
a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestListInfoSubcommand.java
 
b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestListInfoSubcommand.java
index 0f53d85463d..f24fe6e1b2f 100644
--- 
a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestListInfoSubcommand.java
+++ 
b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestListInfoSubcommand.java
@@ -33,6 +33,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
 import java.util.regex.Matcher;
@@ -42,6 +43,8 @@
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
 import picocli.CommandLine;
 
 /**
@@ -212,6 +215,111 @@ public void testDataNodeByUuidOutput()
         "Expected UUID " + nodes.get(0).getNodeID().getUuid() + " but got: " + 
uuid);
   }
 
+  @ParameterizedTest
+  @CsvSource({
+      "true, --most-used, descending",
+      "false, --least-used, ascending"
+  })
+  public void testUsedOrderingAndOutput(
+      boolean mostUsed,
+      String cliFlag,
+      String orderDirection) throws Exception {
+
+    ScmClient scmClient = mock(ScmClient.class);
+    List<HddsProtos.DatanodeUsageInfoProto> usageList = new ArrayList<>();
+    List<HddsProtos.Node> nodeList = getNodeDetails();
+
+    for (int i = 0; i < 4; i++) {
+      long remaining = 1000 - (100 * i);
+      usageList.add(HddsProtos.DatanodeUsageInfoProto.newBuilder()
+          .setNode(nodeList.get(i).getNodeID())
+          .setRemaining(remaining)
+          .setCapacity(1000)
+          .build());
+
+      
when(scmClient.queryNode(UUID.fromString(nodeList.get(i).getNodeID().getUuid())))
+          .thenReturn(nodeList.get(i));
+    }
+
+    if (mostUsed) {
+      Collections.reverse(usageList); // For most-used only
+    }
+
+    when(scmClient.getDatanodeUsageInfo(mostUsed, 
Integer.MAX_VALUE)).thenReturn(usageList);
+    when(scmClient.listPipelines()).thenReturn(new ArrayList<>());
+
+    // ----- JSON output test -----
+    CommandLine c = new CommandLine(cmd);
+    c.parseArgs(cliFlag, "--json");
+    cmd.execute(scmClient);
+
+    String jsonOutput = outContent.toString(DEFAULT_ENCODING);
+    JsonNode root = new ObjectMapper().readTree(jsonOutput);
+
+    assertTrue(root.isArray(), "JSON output should be an array");
+    assertEquals(4, root.size(), "Expected 4 nodes in JSON output");
+
+    for (JsonNode node : root) {
+      assertTrue(node.has("used"), "Missing 'used'");
+      assertTrue(node.has("capacity"), "Missing 'capacity'");
+      assertTrue(node.has("percentUsed"), "Missing 'percentUsed'");
+    }
+
+    validateOrdering(root, orderDirection);
+
+    outContent.reset();
+
+    // ----- Text output test -----
+    c = new CommandLine(cmd);
+    c.parseArgs(cliFlag);
+    cmd.execute(scmClient);
+
+    String textOutput = outContent.toString(DEFAULT_ENCODING);
+    validateOrderingFromTextOutput(textOutput, orderDirection);
+  }
+
+  private void validateOrdering(JsonNode root, String orderDirection) {
+    for (int i = 0; i < root.size() - 1; i++) {
+      long usedCurrent = root.get(i).get("used").asLong();
+      long capacityCurrent = root.get(i).get("capacity").asLong();
+      long usedNext = root.get(i + 1).get("used").asLong();
+      long capacityNext = root.get(i + 1).get("capacity").asLong();
+      double ratio1 = (capacityCurrent == 0) ? 0.0 : (double) usedCurrent / 
capacityCurrent;
+      double ratio2 = (capacityNext == 0) ? 0.0 : (double) usedNext / 
capacityNext;
+
+      if ("ascending".equals(orderDirection)) {
+        assertTrue(ratio1 <= ratio2, "Expected ascending order, got: " + 
ratio1 + " > " + ratio2);
+      } else {
+        assertTrue(ratio1 >= ratio2, "Expected descending order, got: " + 
ratio1 + " < " + ratio2);
+      }
+    }
+  }
+
+  private void validateOrderingFromTextOutput(String output, String 
orderDirection) {
+    Pattern usedPattern = Pattern.compile("Used: (\\d+)");
+    Pattern capacityPattern = Pattern.compile("Capacity: (\\d+)");
+    Matcher usedMatcher = usedPattern.matcher(output);
+    Matcher capacityMatcher = capacityPattern.matcher(output);
+
+    List<Double> usageRatios = new ArrayList<>();
+    while (usedMatcher.find() && capacityMatcher.find()) {
+      long used = Long.parseLong(usedMatcher.group(1));
+      long capacity = Long.parseLong(capacityMatcher.group(1));
+      double usage = (capacity == 0) ? 0.0 : (double) used / capacity;
+      usageRatios.add(usage);
+    }
+
+    for (int i = 0; i < usageRatios.size() - 1; i++) {
+      double ratio1 = usageRatios.get(i);
+      double ratio2 = usageRatios.get(i + 1);
+      if ("ascending".equals(orderDirection)) {
+        assertTrue(ratio1 <= ratio2, "Expected ascending order, got: " + 
ratio1 + " > " + ratio2);
+      } else {
+        assertTrue(ratio1 >= ratio2, "Expected descending order, got: " + 
ratio1 + " < " + ratio2);
+      }
+    }
+  }
+
   private List<HddsProtos.Node> getNodeDetails() {
     List<HddsProtos.Node> nodes = new ArrayList<>();
 


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

Reply via email to