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 19c39698dec..aeb894564b5 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 peerRoles; + private final boolean scmRatisEnabled; /** * Builder for ScmInfo. @@ -38,6 +39,7 @@ public static class Builder { private String clusterId; private String scmId; private final List peerRoles; + private boolean scmRatisEnabled; public Builder() { peerRoles = new ArrayList<>(); @@ -73,15 +75,28 @@ public Builder setRatisPeerRoles(List roles) { 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 peerRoles) { + private ScmInfo(String clusterId, String scmId, List peerRoles, boolean ratisEnabled) { this.clusterId = clusterId; this.scmId = scmId; this.peerRoles = Collections.unmodifiableList(peerRoles); + this.scmRatisEnabled = ratisEnabled; } /** @@ -107,4 +122,8 @@ public String getScmId() { public List 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 2ca3e947438..c41e516d7a6 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 @@ StartContainerBalancerResponseProto startContainerBalancer( */ List 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 d5972cfe076..a5fdfea0f6c 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 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 scmRatisRolesToCheck = Arrays.asList("leader", "follower"); + /** * Creates a new StorageContainerLocationProtocolClientSideTranslatorPB. * @@ -760,8 +767,23 @@ public ScmInfo getScmInfo() throws IOException { .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 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 6cd4f6235ce..a47fa8ac3df 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 40d153a6bb4..33ea27923be 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 ScmInfo getScmInfo() { 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 ScmInfo getScmInfo() { 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 76334d124ea..220abd1fcdc 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 List getScmRatisRoles() throws IOException { 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 29727548561..f1628939451 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 54e44bce36b..3513ec12de1 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 2a25dfbd103..bdeba91e411 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.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 { 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 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 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 @@ private void printOmServerRolesAsJson(List serviceList) 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 480133e59b4..da74083de3b 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.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 RATIS_SCM_ROLES_HEADER = Arrays.asList( + "Host Name", "Ratis Port", "Role", "Node ID", "Host Address"); + + private static final List 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 ratisRoles = scmClient.getScmRatisRoles(); + boolean isRatisEnabled = scmClient.isScmRatisEnable(); if (json) { Map> 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 00000000000..050f1b06e7a --- /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 tableRows; + /** Maps the maximum length of each column. */ + private Map 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 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 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 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 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 data; + TableRow(TableRowType tableRowType, List 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 00000000000..346b448cc25 --- /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 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 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 |"); + } + } +}