Skip to content

Commit cbb7e1a

Browse files
committed
HDDS-11268. [CLI] Improve CLI Display OM/SCM Roles.
1 parent 5118f23 commit cbb7e1a

3 files changed

Lines changed: 321 additions & 0 deletions

File tree

hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/GetServiceRolesSubcommand.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRoleInfo;
2626
import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
2727
import org.apache.hadoop.ozone.om.helpers.ServiceInfo;
28+
import org.apache.hadoop.ozone.utils.FormattingCLIUtils;
2829
import picocli.CommandLine;
2930

3031
import java.io.IOException;
3132
import java.util.ArrayList;
33+
import java.util.Arrays;
3234
import java.util.HashMap;
3335
import java.util.List;
3436
import java.util.Map;
@@ -57,14 +59,36 @@ public class GetServiceRolesSubcommand implements Callable<Void> {
5759
description = "Format output as JSON")
5860
private boolean json;
5961

62+
@CommandLine.Option(names = { "--table" },
63+
defaultValue = "false",
64+
description = "Format output as Table")
65+
private boolean table;
66+
6067
private OzoneManagerProtocol ozoneManagerClient;
6168

69+
private static final String OM_ROLES_TITLE = "Ozone Manager Roles";
70+
71+
private static final List<String> OM_ROLES_HEADER = Arrays.asList(
72+
"Host Name", "Node ID", "Role");
73+
6274
@Override
6375
public Void call() throws Exception {
6476
try {
6577
ozoneManagerClient = parent.createOmClient(omServiceId);
6678
if (json) {
6779
printOmServerRolesAsJson(ozoneManagerClient.getServiceList());
80+
} else if (table) {
81+
FormattingCLIUtils formattingCLIUtils = new FormattingCLIUtils(OM_ROLES_TITLE)
82+
.addHeaders(OM_ROLES_HEADER);
83+
List<ServiceInfo> serviceList = ozoneManagerClient.getServiceList();
84+
for (ServiceInfo serviceInfo : serviceList) {
85+
OMRoleInfo omRoleInfo = serviceInfo.getOmRoleInfo();
86+
if (omRoleInfo != null &&
87+
serviceInfo.getNodeType() == HddsProtos.NodeType.OM) {
88+
formattingCLIUtils.addLine(serviceInfo.getHostname(), omRoleInfo.getNodeId(), omRoleInfo.getServerRole());
89+
}
90+
}
91+
System.out.println(formattingCLIUtils.render());
6892
} else {
6993
printOmServerRoles(ozoneManagerClient.getServiceList());
7094
}

hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/GetScmRatisRolesSubcommand.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.hadoop.ozone.admin.scm;
1919

2020
import java.io.IOException;
21+
import java.util.Arrays;
2122
import java.util.Collections;
2223
import java.util.HashMap;
2324
import java.util.List;
@@ -27,6 +28,7 @@
2728
import org.apache.hadoop.hdds.scm.cli.ScmSubcommand;
2829
import org.apache.hadoop.hdds.scm.client.ScmClient;
2930
import org.apache.hadoop.hdds.server.JsonUtils;
31+
import org.apache.hadoop.ozone.utils.FormattingCLIUtils;
3032
import picocli.CommandLine;
3133

3234
import static java.lang.System.err;
@@ -50,13 +52,31 @@ public class GetScmRatisRolesSubcommand extends ScmSubcommand {
5052
description = "Format output as JSON")
5153
private boolean json;
5254

55+
@CommandLine.Option(names = { "--table" },
56+
defaultValue = "false",
57+
description = "Format output as Table")
58+
private boolean table;
59+
60+
private static final String SCM_ROLES_TITLE = "Storage Container Manager Roles";
61+
62+
private static final List<String> SCM_ROLES_HEADER = Arrays.asList(
63+
"Host Name", "Ratis Port", "Node ID", "Role", "Host Address");
64+
5365
@Override
5466
protected void execute(ScmClient scmClient) throws IOException {
5567
List<String> ratisRoles = scmClient.getScmRatisRoles();
5668
if (json) {
5769
Map<String, Map<String, String>> scmRoles = parseScmRoles(ratisRoles);
5870
System.out.print(
5971
JsonUtils.toJsonStringWithDefaultPrettyPrinter(scmRoles));
72+
} else if (table) {
73+
FormattingCLIUtils formattingCLIUtils = new FormattingCLIUtils(SCM_ROLES_TITLE)
74+
.addHeaders(SCM_ROLES_HEADER);
75+
for (String role : ratisRoles) {
76+
String[] roleItems = role.split(":");
77+
formattingCLIUtils.addLine(roleItems[0], roleItems[1], roleItems[3], roleItems[2], roleItems[4]);
78+
}
79+
System.out.println(formattingCLIUtils.render());
6080
} else {
6181
for (String role: ratisRoles) {
6282
System.out.println(role);
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.ozone.utils;
19+
20+
import java.util.ArrayList;
21+
import java.util.HashMap;
22+
import java.util.List;
23+
import java.util.Map;
24+
25+
/**
26+
* The main core class that generates the ASCII TABLE.
27+
*/
28+
public final class FormattingCLIUtils {
29+
/** Table title. */
30+
private String title;
31+
/** Last processed row type. */
32+
private TableRowType lastTableRowType;
33+
/** StringBuilder object used to concatenate strings. */
34+
private StringBuilder join;
35+
/** An ordered Map that holds each row of data. */
36+
private List<TableRow> tableRows;
37+
/** Maps the maximum length of each column. */
38+
private Map<Integer, Integer> maxColMap;
39+
40+
/**
41+
* Contains the title constructor.
42+
* @param title titleName
43+
*/
44+
public FormattingCLIUtils(String title) {
45+
this.init();
46+
this.title = title;
47+
}
48+
49+
/**
50+
* Initialize the data.
51+
*/
52+
private void init() {
53+
this.join = new StringBuilder();
54+
this.tableRows = new ArrayList<>();
55+
this.maxColMap = new HashMap<>();
56+
}
57+
58+
/**
59+
* Adds elements from the collection to the header data in the table.
60+
* @param headers Header data
61+
* @return FormattingCLIUtils object
62+
*/
63+
public FormattingCLIUtils addHeaders(List<?> headers) {
64+
return this.appendRows(TableRowType.HEADER, headers.toArray());
65+
}
66+
67+
/**
68+
* Adds a row of normal data to the table.
69+
* @param objects Common row data
70+
* @return FormattingCLIUtils object
71+
*/
72+
public FormattingCLIUtils addLine(Object... objects) {
73+
return this.appendRows(TableRowType.LINE, objects);
74+
}
75+
76+
/**
77+
* Adds the middle row of data to the table.
78+
* @param tableRowType TableRowType
79+
* @param objects Table row data
80+
* @return FormattingCLIUtils object
81+
*/
82+
private FormattingCLIUtils appendRows(TableRowType tableRowType, Object... objects) {
83+
if (objects != null && objects.length > 0) {
84+
int len = objects.length;
85+
if (this.maxColMap.size() > len) {
86+
throw new IllegalArgumentException("The number of columns that inserted a row " +
87+
"of data into the table is different from the number of previous columns, check!");
88+
}
89+
List<String> lines = new ArrayList<>();
90+
for (int i = 0; i < len; i++) {
91+
Object o = objects[i];
92+
String value = o == null ? "null" : o.toString();
93+
lines.add(value);
94+
Integer maxColSize = this.maxColMap.get(i);
95+
if (maxColSize == null) {
96+
this.maxColMap.put(i, value.length());
97+
continue;
98+
}
99+
if (value.length() > maxColSize) {
100+
this.maxColMap.put(i, value.length());
101+
}
102+
}
103+
this.tableRows.add(new TableRow(tableRowType, lines));
104+
}
105+
return this;
106+
}
107+
108+
/**
109+
* Builds the string for the row of the table title.
110+
*/
111+
private void buildTitle() {
112+
if (this.title != null) {
113+
int maxTitleSize = 0;
114+
for (Integer maxColSize : this.maxColMap.values()) {
115+
maxTitleSize += maxColSize;
116+
}
117+
maxTitleSize += 3 * (this.maxColMap.size() - 1);
118+
if (this.title.length() > maxTitleSize) {
119+
this.title = this.title.substring(0, maxTitleSize);
120+
}
121+
this.join.append("+");
122+
for (int i = 0; i < maxTitleSize + 2; i++) {
123+
this.join.append("-");
124+
}
125+
this.join.append("+\n")
126+
.append("|")
127+
.append(StrUtils.center(this.title, maxTitleSize + 2, ' '))
128+
.append("|\n");
129+
this.lastTableRowType = TableRowType.TITLE;
130+
}
131+
}
132+
133+
/**
134+
* Build the table, first build the title, and then walk through each row of data to build.
135+
*/
136+
private void buildTable() {
137+
this.buildTitle();
138+
for (int i = 0, len = this.tableRows.size(); i < len; i++) {
139+
List<String> data = this.tableRows.get(i).data;
140+
switch (this.tableRows.get(i).tableRowType) {
141+
case HEADER:
142+
if (this.lastTableRowType != TableRowType.HEADER) {
143+
this.buildRowBorder(data);
144+
}
145+
this.buildRowLine(data);
146+
this.buildRowBorder(data);
147+
break;
148+
case LINE:
149+
this.buildRowLine(data);
150+
if (i == len - 1) {
151+
this.buildRowBorder(data);
152+
}
153+
break;
154+
default:
155+
break;
156+
}
157+
}
158+
}
159+
160+
/**
161+
* Method to build a border row.
162+
* @param data dataLine
163+
*/
164+
private void buildRowBorder(List<String> data) {
165+
this.join.append("+");
166+
for (int i = 0, len = data.size(); i < len; i++) {
167+
for (int j = 0; j < this.maxColMap.get(i) + 2; j++) {
168+
this.join.append("-");
169+
}
170+
this.join.append("+");
171+
}
172+
this.join.append("\n");
173+
}
174+
175+
/**
176+
* A way to build rows of data.
177+
* @param data dataLine
178+
*/
179+
private void buildRowLine(List<String> data) {
180+
this.join.append("|");
181+
for (int i = 0, len = data.size(); i < len; i++) {
182+
this.join.append(StrUtils.center(data.get(i), this.maxColMap.get(i) + 2, ' '))
183+
.append("|");
184+
}
185+
this.join.append("\n");
186+
}
187+
188+
/**
189+
* Rendering is born as a result.
190+
* @return ASCII string of Table
191+
*/
192+
public String render() {
193+
this.buildTable();
194+
return this.join.toString();
195+
}
196+
197+
/**
198+
* The type of each table row and the entity class of the data.
199+
*/
200+
private static class TableRow {
201+
private TableRowType tableRowType;
202+
private List<String> data;
203+
TableRow(TableRowType tableRowType, List<String> data) {
204+
this.tableRowType = tableRowType;
205+
this.data = data;
206+
}
207+
}
208+
209+
/**
210+
* An enumeration class that distinguishes between table headers and normal table data.
211+
*/
212+
private enum TableRowType {
213+
TITLE, HEADER, LINE
214+
}
215+
216+
/**
217+
* String utility class.
218+
*/
219+
private static final class StrUtils {
220+
/**
221+
* Puts a string in the middle of a given size.
222+
* @param str Character string
223+
* @param size Total size
224+
* @param padChar Fill character
225+
* @return String result
226+
*/
227+
private static String center(String str, int size, char padChar) {
228+
if (str != null && size > 0) {
229+
int strLen = str.length();
230+
int pads = size - strLen;
231+
if (pads > 0) {
232+
str = leftPad(str, strLen + pads / 2, padChar);
233+
str = rightPad(str, size, padChar);
234+
}
235+
}
236+
return str;
237+
}
238+
239+
/**
240+
* Left-fill the given string and size.
241+
* @param str String
242+
* @param size totalSize
243+
* @param padChar Fill character
244+
* @return String result
245+
*/
246+
private static String leftPad(final String str, int size, char padChar) {
247+
int pads = size - str.length();
248+
return pads <= 0 ? str : repeat(padChar, pads).concat(str);
249+
}
250+
251+
/**
252+
* Right-fill the given string and size.
253+
* @param str String
254+
* @param size totalSize
255+
* @param padChar Fill character
256+
* @return String result
257+
*/
258+
private static String rightPad(final String str, int size, char padChar) {
259+
int pads = size - str.length();
260+
return pads <= 0 ? str : str.concat(repeat(padChar, pads));
261+
}
262+
263+
/**
264+
* Re-fill characters as strings.
265+
* @param ch String
266+
* @param repeat Number of repeats
267+
* @return String
268+
*/
269+
private static String repeat(char ch, int repeat) {
270+
char[] buf = new char[repeat];
271+
for (int i = repeat - 1; i >= 0; i--) {
272+
buf[i] = ch;
273+
}
274+
return new String(buf);
275+
}
276+
}
277+
}

0 commit comments

Comments
 (0)