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

adoroszlai 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 eb26677e61 HDDS-11268. Add --table mode for OM/SCM Roles CLI (#7016)
eb26677e61 is described below

commit eb26677e61d3e1af839051c0638eeb2b37bef22e
Author: slfan1989 <[email protected]>
AuthorDate: Sat Oct 5 16:16:22 2024 +0800

    HDDS-11268. Add --table mode for OM/SCM Roles CLI (#7016)
---
 .../java/org/apache/hadoop/hdds/scm/ScmInfo.java   |  23 +-
 .../apache/hadoop/hdds/scm/client/ScmClient.java   |   9 +
 ...inerLocationProtocolClientSideTranslatorPB.java |  24 +-
 .../interface-client/src/main/proto/hdds.proto     |   1 +
 .../hdds/scm/server/SCMClientProtocolServer.java   |   2 +
 .../hdds/scm/cli/ContainerOperationClient.java     |   5 +
 .../dist/src/main/smoketest/admincli/scmrole.robot |   6 +-
 .../dist/src/main/smoketest/omha/om-roles.robot    |  15 ++
 .../ozone/admin/om/GetServiceRolesSubcommand.java  |  36 +++
 .../admin/scm/GetScmRatisRolesSubcommand.java      |  35 ++-
 .../hadoop/ozone/utils/FormattingCLIUtils.java     | 291 +++++++++++++++++++++
 .../ozone/scm/TestGetScmRatisRolesSubcommand.java  |  87 ++++++
 12 files changed, 529 insertions(+), 5 deletions(-)

diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmInfo.java 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmInfo.java
index 19c39698de..aeb894564b 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmInfo.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmInfo.java
@@ -30,6 +30,7 @@ public final class ScmInfo {
   private final String clusterId;
   private final String scmId;
   private final List<String> peerRoles;
+  private final boolean scmRatisEnabled;
 
   /**
    * Builder for ScmInfo.
@@ -38,6 +39,7 @@ public final class ScmInfo {
     private String clusterId;
     private String scmId;
     private final List<String> peerRoles;
+    private boolean scmRatisEnabled;
 
     public Builder() {
       peerRoles = new ArrayList<>();
@@ -73,15 +75,28 @@ public final class ScmInfo {
       return this;
     }
 
+    /**
+     * Set whether SCM enables Ratis.
+     *
+     * @param ratisEnabled If it is true, it means that the Ratis mode is 
turned on.
+     * If it is false, it means that the Ratis mode is not turned on.
+     * @return Builder for scmInfo
+     */
+    public Builder setScmRatisEnabled(boolean ratisEnabled) {
+      scmRatisEnabled = ratisEnabled;
+      return this;
+    }
+
     public ScmInfo build() {
-      return new ScmInfo(clusterId, scmId, peerRoles);
+      return new ScmInfo(clusterId, scmId, peerRoles, scmRatisEnabled);
     }
   }
 
-  private ScmInfo(String clusterId, String scmId, List<String> peerRoles) {
+  private ScmInfo(String clusterId, String scmId, List<String> peerRoles, 
boolean ratisEnabled) {
     this.clusterId = clusterId;
     this.scmId = scmId;
     this.peerRoles = Collections.unmodifiableList(peerRoles);
+    this.scmRatisEnabled = ratisEnabled;
   }
 
   /**
@@ -107,4 +122,8 @@ public final class ScmInfo {
   public List<String> getRatisPeerRoles() {
     return peerRoles;
   }
+
+  public boolean getScmRatisEnabled() {
+    return scmRatisEnabled;
+  }
 }
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 2ca3e94743..c41e516d7a 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
@@ -392,6 +392,15 @@ public interface ScmClient extends Closeable {
    */
   List<String> getScmRatisRoles() throws IOException;
 
+  /**
+   * Get the current SCM mode.
+   *
+   * @return `true` indicates that it is in RATIS mode,
+   * while `false` indicates that it is in STANDALONE mode.
+   * @throws IOException  an I/O exception of some sort has occurred.
+   */
+  boolean isScmRatisEnable() throws IOException;
+
   /**
    * Force generates new secret keys (rotate).
    *
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 d5972cfe07..a5fdfea0f6 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
@@ -128,6 +128,7 @@ import org.apache.hadoop.ozone.util.ProtobufUtils;
 
 import java.io.Closeable;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -157,6 +158,12 @@ public final class 
StorageContainerLocationProtocolClientSideTranslatorPB
   private final StorageContainerLocationProtocolPB rpcProxy;
   private final SCMContainerLocationFailoverProxyProvider fpp;
 
+  /**
+   * This is used to check if 'leader' or 'follower' exists,
+   * in order to confirm whether we have enabled Ratis.
+   */
+  private final List<String> scmRatisRolesToCheck = Arrays.asList("leader", 
"follower");
+
   /**
    * Creates a new StorageContainerLocationProtocolClientSideTranslatorPB.
    *
@@ -760,8 +767,23 @@ public final class 
StorageContainerLocationProtocolClientSideTranslatorPB
         .setScmId(resp.getScmId())
         .setRatisPeerRoles(resp.getPeerRolesList());
 
-    return builder.build();
+    // By default, we assume that SCM Ratis is not enabled.
 
+    // If the response contains the `ScmRatisEnabled` field,
+    // we will set it directly; otherwise,
+    // we will determine if Ratis is enabled based on
+    // whether the `peerRolesList` contains the keywords 'leader' or 
'follower'.
+    if (resp.hasScmRatisEnabled()) {
+      builder.setScmRatisEnabled(resp.getScmRatisEnabled());
+    } else {
+      List<String> peerRolesList = resp.getPeerRolesList();
+      if (!peerRolesList.isEmpty()) {
+        boolean containsScmRoles = 
peerRolesList.stream().map(String::toLowerCase)
+            .anyMatch(scmRatisRolesToCheck::contains);
+        builder.setScmRatisEnabled(containsScmRoles);
+      }
+    }
+    return builder.build();
   }
 
   @Override
diff --git a/hadoop-hdds/interface-client/src/main/proto/hdds.proto 
b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
index 6cd4f6235c..a47fa8ac3d 100644
--- a/hadoop-hdds/interface-client/src/main/proto/hdds.proto
+++ b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
@@ -257,6 +257,7 @@ message GetScmInfoResponseProto {
     required string clusterId = 1;
     required string scmId = 2;
     repeated string peerRoles = 3;
+    optional bool scmRatisEnabled = 4;
 }
 
 message AddScmRequestProto {
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 40d153a6bb..33ea27923b 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
@@ -837,6 +837,7 @@ public class SCMClientProtocolServer implements
       if (scm.getScmHAManager().getRatisServer() != null) {
         builder.setRatisPeerRoles(
             scm.getScmHAManager().getRatisServer().getRatisRoles());
+        builder.setScmRatisEnabled(true);
       } else {
         // In case, there is no ratis, there is no ratis role.
         // This will just print the hostname with ratis port as the default
@@ -844,6 +845,7 @@ public class SCMClientProtocolServer implements
         String address = scm.getSCMHANodeDetails().getLocalNodeDetails()
             .getRatisHostPortStr();
         builder.setRatisPeerRoles(Arrays.asList(address));
+        builder.setScmRatisEnabled(false);
       }
       return builder.build();
     } catch (Exception ex) {
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 76334d124e..220abd1fcd 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
@@ -519,6 +519,11 @@ public class ContainerOperationClient implements ScmClient 
{
     return storageContainerLocationClient.getScmInfo().getRatisPeerRoles();
   }
 
+  @Override
+  public boolean isScmRatisEnable() throws IOException {
+    return storageContainerLocationClient.getScmInfo().getScmRatisEnabled();
+  }
+
   @Override
   public boolean rotateSecretKeys(boolean force) throws IOException {
     return secretKeyClient.checkAndRotate(force);
diff --git a/hadoop-ozone/dist/src/main/smoketest/admincli/scmrole.robot 
b/hadoop-ozone/dist/src/main/smoketest/admincli/scmrole.robot
index 2972754856..f162893945 100644
--- a/hadoop-ozone/dist/src/main/smoketest/admincli/scmrole.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/admincli/scmrole.robot
@@ -30,4 +30,8 @@ Run scm roles
 List scm roles as JSON
     ${output} =         Execute          ozone admin scm roles --json
     ${leader} =         Execute          echo '${output}' | jq -r '.[] | 
select(.raftPeerRole == "LEADER")'
-                        Should Not Be Equal       ${leader}       ${EMPTY}
\ No newline at end of file
+                        Should Not Be Equal       ${leader}       ${EMPTY}
+
+List scm roles as TABLE
+    ${output} =         Execute          ozone admin scm roles --table
+                        Should Match Regexp   ${output}  \\|.*LEADER.*
\ No newline at end of file
diff --git a/hadoop-ozone/dist/src/main/smoketest/omha/om-roles.robot 
b/hadoop-ozone/dist/src/main/smoketest/omha/om-roles.robot
index 54e44bce36..3513ec12de 100644
--- a/hadoop-ozone/dist/src/main/smoketest/omha/om-roles.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/omha/om-roles.robot
@@ -28,6 +28,9 @@ Assert Leader Present in JSON
      [Arguments]                     ${output}
      ${leader} =                     Execute                        echo 
'${output}' | jq '.[] | select(.[] | .serverRole == "LEADER")'
                                      Should Not Be Equal            ${leader}  
     ${EMPTY}
+Assert Leader Present in TABLE
+     [Arguments]                     ${output}
+     Should Match Regexp             ${output}                      
\\|.*LEADER.*
 
 *** Test Cases ***
 List om roles with OM service ID passed
@@ -53,3 +56,15 @@ List om roles as JSON without OM service ID passed
                                     Assert Leader Present in JSON   
${output_without_id_passed}
     ${output_without_id_passed} =   Execute And Ignore Error        ozone 
admin --set=ozone.om.service.ids=omservice,omservice2 om roles --json
                                     Should Contain                  
${output_without_id_passed}      no Ozone Manager service ID specified
+
+List om roles as TABLE with OM service ID passed
+    ${output_with_id_passed} =      Execute                         ozone 
admin om roles --service-id=omservice --table
+                                    Assert Leader Present in TABLE  
${output_with_id_passed}
+    ${output_with_id_passed} =      Execute                         ozone 
admin --set=ozone.om.service.ids=omservice,omservice2 om roles 
--service-id=omservice --table
+                                    Assert Leader Present in TABLE  
${output_with_id_passed}
+
+List om roles as TABLE without OM service ID passed
+    ${output_without_id_passed} =   Execute                         ozone 
admin om roles --table
+                                    Assert Leader Present in TABLE  
${output_without_id_passed}
+    ${output_without_id_passed} =   Execute And Ignore Error        ozone 
admin --set=ozone.om.service.ids=omservice,omservice2 om roles --table
+                                    Should Contain                  
${output_without_id_passed}      no Ozone Manager service ID specified
\ No newline at end of file
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/GetServiceRolesSubcommand.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/GetServiceRolesSubcommand.java
index 2a25dfbd10..bdeba91e41 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/GetServiceRolesSubcommand.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/GetServiceRolesSubcommand.java
@@ -18,6 +18,7 @@
 
 package org.apache.hadoop.ozone.admin.om;
 
+import org.apache.hadoop.classification.VisibleForTesting;
 import org.apache.hadoop.hdds.cli.HddsVersionProvider;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
 import org.apache.hadoop.hdds.server.JsonUtils;
@@ -25,10 +26,12 @@ import org.apache.hadoop.ozone.client.OzoneClientException;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRoleInfo;
 import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
 import org.apache.hadoop.ozone.om.helpers.ServiceInfo;
+import org.apache.hadoop.ozone.utils.FormattingCLIUtils;
 import picocli.CommandLine;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -57,14 +60,37 @@ public class GetServiceRolesSubcommand implements 
Callable<Void> {
       description = "Format output as JSON")
   private boolean json;
 
+  @CommandLine.Option(names = { "--table" },
+       defaultValue = "false",
+       description = "Format output as Table")
+  private boolean table;
+
   private OzoneManagerProtocol ozoneManagerClient;
 
+  private static final String OM_ROLES_TITLE = "Ozone Manager Roles";
+
+  private static final List<String> OM_ROLES_HEADER = Arrays.asList(
+      "Host Name", "Node ID", "Role");
+
   @Override
   public Void call() throws Exception {
     try {
       ozoneManagerClient =  parent.createOmClient(omServiceId);
       if (json) {
         printOmServerRolesAsJson(ozoneManagerClient.getServiceList());
+      } else if (table) {
+        FormattingCLIUtils formattingCLIUtils = new 
FormattingCLIUtils(OM_ROLES_TITLE)
+            .addHeaders(OM_ROLES_HEADER);
+        List<ServiceInfo> serviceList = ozoneManagerClient.getServiceList();
+        for (ServiceInfo serviceInfo : serviceList) {
+          OMRoleInfo omRoleInfo = serviceInfo.getOmRoleInfo();
+          if (omRoleInfo != null &&
+               serviceInfo.getNodeType() == HddsProtos.NodeType.OM) {
+            formattingCLIUtils.addLine(new String[]{serviceInfo.getHostname(),
+                omRoleInfo.getNodeId(), omRoleInfo.getServerRole()});
+          }
+        }
+        System.out.println(formattingCLIUtils.render());
       } else {
         printOmServerRoles(ozoneManagerClient.getServiceList());
       }
@@ -110,4 +136,14 @@ public class GetServiceRolesSubcommand implements 
Callable<Void> {
     System.out.print(
         JsonUtils.toJsonStringWithDefaultPrettyPrinter(omServiceList));
   }
+
+  @VisibleForTesting
+  public void setOzoneManagerClient(OzoneManagerProtocol ozoneManagerClient) {
+    this.ozoneManagerClient = ozoneManagerClient;
+  }
+
+  @VisibleForTesting
+  public void setParent(OMAdmin parent) {
+    this.parent = parent;
+  }
 }
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/GetScmRatisRolesSubcommand.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/GetScmRatisRolesSubcommand.java
index 480133e59b..da74083de3 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/GetScmRatisRolesSubcommand.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/GetScmRatisRolesSubcommand.java
@@ -18,6 +18,7 @@
 package org.apache.hadoop.ozone.admin.scm;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -27,6 +28,7 @@ import org.apache.hadoop.hdds.cli.HddsVersionProvider;
 import org.apache.hadoop.hdds.scm.cli.ScmSubcommand;
 import org.apache.hadoop.hdds.scm.client.ScmClient;
 import org.apache.hadoop.hdds.server.JsonUtils;
+import org.apache.hadoop.ozone.utils.FormattingCLIUtils;
 import picocli.CommandLine;
 
 import static java.lang.System.err;
@@ -50,13 +52,44 @@ public class GetScmRatisRolesSubcommand extends 
ScmSubcommand {
       description = "Format output as JSON")
   private boolean json;
 
+  @CommandLine.Option(names = { "--table" },
+       defaultValue = "false",
+       description = "Format output as Table")
+  private boolean table;
+
+  private static final String SCM_ROLES_TITLE = "Storage Container Manager 
Roles";
+
+  private static final List<String> RATIS_SCM_ROLES_HEADER = Arrays.asList(
+      "Host Name", "Ratis Port", "Role",  "Node ID", "Host Address");
+
+  private static final List<String> STANDALONE_SCM_ROLES_HEADER = 
Arrays.asList("Host Name", "Port");
+
   @Override
-  protected void execute(ScmClient scmClient) throws IOException {
+  public void execute(ScmClient scmClient) throws IOException {
     List<String> ratisRoles = scmClient.getScmRatisRoles();
+    boolean isRatisEnabled = scmClient.isScmRatisEnable();
     if (json) {
       Map<String, Map<String, String>> scmRoles = parseScmRoles(ratisRoles);
       System.out.print(
           JsonUtils.toJsonStringWithDefaultPrettyPrinter(scmRoles));
+    } else if (table) {
+      FormattingCLIUtils formattingCLIUtils = new 
FormattingCLIUtils(SCM_ROLES_TITLE);
+
+      // Determine which header to use based on whether Ratis is enabled or 
not.
+      if (isRatisEnabled) {
+        formattingCLIUtils.addHeaders(RATIS_SCM_ROLES_HEADER);
+      } else {
+        formattingCLIUtils.addHeaders(STANDALONE_SCM_ROLES_HEADER);
+      }
+
+      for (String role : ratisRoles) {
+        String[] roleItems = role.split(":");
+        if (roleItems.length < 2) {
+          err.println("Invalid response received for ScmRatisRoles.");
+        }
+        formattingCLIUtils.addLine(roleItems);
+      }
+      System.out.println(formattingCLIUtils.render());
     } else {
       for (String role: ratisRoles) {
         System.out.println(role);
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/utils/FormattingCLIUtils.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/utils/FormattingCLIUtils.java
new file mode 100644
index 0000000000..050f1b06e7
--- /dev/null
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/utils/FormattingCLIUtils.java
@@ -0,0 +1,291 @@
+/**
+ * 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
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.ozone.utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * We define this class to output information in a tabular format,
+ * making the printed information easier to read.
+ *
+ * For example, in OM output:
+ * If it's in HA mode:
+ *
+ * +------------------------------------------------------+
+ * |                 Ozone Manager Roles                  |
+ * +---------------------------------+---------+----------+
+ * |            Host Name            | Node ID |   Role   |
+ * +---------------------------------+---------+----------+
+ * | bigdata-ozone-online32          |  om32   | FOLLOWER |
+ * | bigdata-ozone-online30          |  om30   | FOLLOWER |
+ * | bigdata-ozone-online31          |  om31   |  LEADER  |
+ * +---------------------------------+---------+----------+
+ */
+public final class FormattingCLIUtils {
+  /** Table title. */
+  private String title;
+  /** Last processed row type. */
+  private TableRowType lastTableRowType;
+  /** StringBuilder object used to concatenate strings. */
+  private StringBuilder join;
+  /** An ordered Map that holds each row of data. */
+  private List<TableRow> tableRows;
+  /** Maps the maximum length of each column. */
+  private Map<Integer, Integer> maxColMap;
+
+  /**
+   * Contains the title constructor.
+   * @param title titleName
+   */
+  public FormattingCLIUtils(String title) {
+    this.init();
+    this.title = title;
+  }
+
+  /**
+   * Initialize the data.
+   */
+  private void init() {
+    this.join = new StringBuilder();
+    this.tableRows = new ArrayList<>();
+    this.maxColMap = new HashMap<>();
+  }
+
+  /**
+   * Adds elements from the collection to the header data in the table.
+   * @param headers Header data
+   * @return FormattingCLIUtils object
+   */
+  public FormattingCLIUtils addHeaders(List<?> headers) {
+    return this.appendRows(TableRowType.HEADER, headers.toArray());
+  }
+
+  /**
+   * Adds a row of normal data to the table.
+   * @param objects Common row data
+   * @return FormattingCLIUtils object
+   */
+  public FormattingCLIUtils addLine(Object[] objects) {
+    return this.appendRows(TableRowType.LINE, objects);
+  }
+
+  /**
+   * Adds the middle row of data to the table.
+   * @param tableRowType TableRowType
+   * @param objects Table row data
+   * @return FormattingCLIUtils object
+   */
+  private FormattingCLIUtils appendRows(TableRowType tableRowType, Object[] 
objects) {
+    if (objects != null && objects.length > 0) {
+      int len = objects.length;
+      if (this.maxColMap.size() > len) {
+        throw new IllegalArgumentException("The number of columns that 
inserted a row " +
+            "of data into the table is different from the number of previous 
columns, check!");
+      }
+      List<String> lines = new ArrayList<>();
+      for (int i = 0; i < len; i++) {
+        Object o = objects[i];
+        String value = o == null ? "null" : o.toString();
+        lines.add(value);
+        Integer maxColSize = this.maxColMap.get(i);
+        if (maxColSize == null) {
+          this.maxColMap.put(i, value.length());
+          continue;
+        }
+        if (value.length() > maxColSize) {
+          this.maxColMap.put(i, value.length());
+        }
+      }
+      this.tableRows.add(new TableRow(tableRowType, lines));
+    }
+    return this;
+  }
+
+  /**
+   * Builds the string for the row of the table title.
+   */
+  private void buildTitle() {
+    if (this.title != null) {
+      int maxTitleSize = 0;
+      for (Integer maxColSize : this.maxColMap.values()) {
+        maxTitleSize += maxColSize;
+      }
+      maxTitleSize += 3 * (this.maxColMap.size() - 1);
+      if (this.title.length() > maxTitleSize) {
+        this.title = this.title.substring(0, maxTitleSize);
+      }
+      this.join.append("+");
+      for (int i = 0; i < maxTitleSize + 2; i++) {
+        this.join.append("-");
+      }
+      this.join.append("+\n")
+          .append("|")
+          .append(StrUtils.center(this.title, maxTitleSize + 2, ' '))
+          .append("|\n");
+      this.lastTableRowType = TableRowType.TITLE;
+    }
+  }
+
+  /**
+   * Build the table, first build the title, and then walk through each row of 
data to build.
+   */
+  private void buildTable() {
+    this.buildTitle();
+    for (int i = 0, len = this.tableRows.size(); i < len; i++) {
+      List<String> data = this.tableRows.get(i).data;
+      switch (this.tableRows.get(i).tableRowType) {
+      case HEADER:
+        if (this.lastTableRowType != TableRowType.HEADER) {
+          this.buildRowBorder(data);
+        }
+        this.buildRowLine(data);
+        this.buildRowBorder(data);
+        break;
+      case LINE:
+        this.buildRowLine(data);
+        if (i == len - 1) {
+          this.buildRowBorder(data);
+        }
+        break;
+      default:
+        break;
+      }
+    }
+  }
+
+  /**
+   * Method to build a border row.
+   * @param data dataLine
+   */
+  private void buildRowBorder(List<String> data) {
+    this.join.append("+");
+    for (int i = 0, len = data.size(); i < len; i++) {
+      for (int j = 0; j < this.maxColMap.get(i) + 2; j++) {
+        this.join.append("-");
+      }
+      this.join.append("+");
+    }
+    this.join.append("\n");
+  }
+
+  /**
+   * A way to build rows of data.
+   * @param data dataLine
+   */
+  private void buildRowLine(List<String> data) {
+    this.join.append("|");
+    for (int i = 0, len = data.size(); i < len; i++) {
+      this.join.append(StrUtils.center(data.get(i), this.maxColMap.get(i) + 2, 
' '))
+          .append("|");
+    }
+    this.join.append("\n");
+  }
+
+  /**
+   * Rendering is born as a result.
+   * @return ASCII string of Table
+   */
+  public String render() {
+    this.buildTable();
+    return this.join.toString();
+  }
+
+  /**
+   * The type of each table row and the entity class of the data.
+   */
+  private static class TableRow {
+    private TableRowType tableRowType;
+    private List<String> data;
+    TableRow(TableRowType tableRowType, List<String> data) {
+      this.tableRowType = tableRowType;
+      this.data = data;
+    }
+  }
+
+  /**
+   * An enumeration class that distinguishes between table headers and normal 
table data.
+   */
+  private enum TableRowType {
+    TITLE, HEADER, LINE
+  }
+
+  /**
+   * String utility class.
+   */
+  private static final class StrUtils {
+    /**
+     * Puts a string in the middle of a given size.
+     * @param str Character string
+     * @param size Total size
+     * @param padChar Fill character
+     * @return String result
+     */
+    private static String center(String str, int size, char padChar) {
+      if (str != null && size > 0) {
+        int strLen = str.length();
+        int pads = size - strLen;
+        if (pads > 0) {
+          str = leftPad(str, strLen + pads / 2, padChar);
+          str = rightPad(str, size, padChar);
+        }
+      }
+      return str;
+    }
+
+    /**
+     * Left-fill the given string and size.
+     * @param str String
+     * @param size totalSize
+     * @param padChar Fill character
+     * @return String result
+     */
+    private static String leftPad(final String str, int size, char padChar) {
+      int pads = size - str.length();
+      return pads <= 0 ? str : repeat(padChar, pads).concat(str);
+    }
+
+    /**
+     * Right-fill the given string and size.
+     * @param str String
+     * @param size totalSize
+     * @param padChar Fill character
+     * @return String result
+     */
+    private static String rightPad(final String str, int size, char padChar) {
+      int pads = size - str.length();
+      return pads <= 0 ? str : str.concat(repeat(padChar, pads));
+    }
+
+    /**
+     * Re-fill characters as strings.
+     * @param ch String
+     * @param repeat Number of repeats
+     * @return String
+     */
+    private static String repeat(char ch, int repeat) {
+      char[] buf = new char[repeat];
+      for (int i = repeat - 1; i >= 0; i--) {
+        buf[i] = ch;
+      }
+      return new String(buf);
+    }
+  }
+}
diff --git 
a/hadoop-ozone/tools/src/test/java/org/apache/hadoop/ozone/scm/TestGetScmRatisRolesSubcommand.java
 
b/hadoop-ozone/tools/src/test/java/org/apache/hadoop/ozone/scm/TestGetScmRatisRolesSubcommand.java
new file mode 100644
index 0000000000..346b448cc2
--- /dev/null
+++ 
b/hadoop-ozone/tools/src/test/java/org/apache/hadoop/ozone/scm/TestGetScmRatisRolesSubcommand.java
@@ -0,0 +1,87 @@
+/*
+ * 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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  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.ozone.scm;
+
+import org.apache.hadoop.hdds.scm.client.ScmClient;
+import org.apache.hadoop.ozone.admin.scm.GetScmRatisRolesSubcommand;
+import org.apache.ozone.test.GenericTestUtils;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import picocli.CommandLine;
+
+/**
+ * This unit test is used to verify whether the output of
+ * `TestGetScmRatisRolesSubcommand` meets the expected results.
+ */
+public class TestGetScmRatisRolesSubcommand {
+
+  @Test
+  public void testGetScmHARatisRoles() throws Exception {
+    GetScmRatisRolesSubcommand cmd = new GetScmRatisRolesSubcommand();
+    ScmClient client = mock(ScmClient.class);
+    CommandLine c = new CommandLine(cmd);
+    c.parseArgs("--table");
+
+    List<String> result = new ArrayList<>();
+    
result.add("bigdata-ozone-online31:9894:FOLLOWER:61b1c8e5-da40-4567-8a17-96a0234ba14e:100.3.197.98");
+    
result.add("bigdata-ozone-online32:9894:LEADER:e428ca07-b2a3-4756-bf9b-a4abb033c7d1:100.3.192.89");
+    
result.add("bigdata-ozone-online30:9894:FOLLOWER:41f90734-b3ee-4284-ad96-40a286654952:100.3.196.51");
+
+    when(client.getScmRatisRoles()).thenAnswer(invocation -> result);
+    when(client.isScmRatisEnable()).thenAnswer(invocation -> true);
+
+    try (GenericTestUtils.SystemOutCapturer capture =
+        new GenericTestUtils.SystemOutCapturer()) {
+      cmd.execute(client);
+      assertThat(capture.getOutput()).contains(
+          "bigdata-ozone-online31 |    9894    | FOLLOWER | 
61b1c8e5-da40-4567-8a17-96a0234ba14e");
+      assertThat(capture.getOutput()).contains(
+          "bigdata-ozone-online32 |    9894    |  LEADER  | 
e428ca07-b2a3-4756-bf9b-a4abb033c7d1");
+      assertThat(capture.getOutput()).contains(
+          "bigdata-ozone-online30 |    9894    | FOLLOWER | 
41f90734-b3ee-4284-ad96-40a286654952");
+    }
+  }
+
+  @Test
+  public void testGetScmStandAloneRoles() throws Exception {
+
+    GetScmRatisRolesSubcommand cmd = new GetScmRatisRolesSubcommand();
+    ScmClient client = mock(ScmClient.class);
+    CommandLine c = new CommandLine(cmd);
+    c.parseArgs("--table");
+
+    List<String> result = new ArrayList<>();
+    result.add("bigdata-ozone-online31:9894");
+
+    when(client.getScmRatisRoles()).thenAnswer(invocation -> result);
+    when(client.isScmRatisEnable()).thenAnswer(invocation -> false);
+
+    try (GenericTestUtils.SystemOutCapturer capture =
+        new GenericTestUtils.SystemOutCapturer()) {
+      cmd.execute(client);
+      assertThat(capture.getOutput()).contains("| bigdata-ozone-online31 | 
9894 |");
+    }
+  }
+}


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

Reply via email to