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]