Skip to content

Commit 2cf6d59

Browse files
authored
HDDS-12553. ozone admin container list should output real JSON array (apache#8050)
1 parent 63fcb27 commit 2cf6d59

5 files changed

Lines changed: 151 additions & 41 deletions

File tree

hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/container/ListSubcommand.java

Lines changed: 96 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@
2121
import com.fasterxml.jackson.annotation.PropertyAccessor;
2222
import com.fasterxml.jackson.databind.ObjectMapper;
2323
import com.fasterxml.jackson.databind.ObjectWriter;
24+
import com.fasterxml.jackson.databind.SequenceWriter;
2425
import com.fasterxml.jackson.databind.SerializationFeature;
2526
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
2627
import com.google.common.base.Strings;
2728
import java.io.IOException;
29+
import java.io.OutputStream;
30+
import java.util.List;
2831
import org.apache.hadoop.hdds.cli.HddsVersionProvider;
2932
import org.apache.hadoop.hdds.client.ReplicationConfig;
3033
import org.apache.hadoop.hdds.client.ReplicationType;
@@ -40,7 +43,13 @@
4043
import picocli.CommandLine.Option;
4144

4245
/**
43-
* This is the handler that process container list command.
46+
* The ListSubcommand class represents a command to list containers in a structured way.
47+
* It provides options to control how the list is generated, including specifying
48+
* starting container ID, maximum number of containers to list, and other filtering criteria
49+
* such as container state or replication type.
50+
*
51+
* This command connects to the SCM (Storage Container Manager) client to fetch the
52+
* container details and outputs the result in a JSON format.
4453
*/
4554
@Command(
4655
name = "list",
@@ -89,13 +98,6 @@ public class ListSubcommand extends ScmSubcommand {
8998
WRITER = mapper.writerWithDefaultPrettyPrinter();
9099
}
91100

92-
93-
private void outputContainerInfo(ContainerInfo containerInfo)
94-
throws IOException {
95-
// Print container report info.
96-
System.out.println(WRITER.writeValueAsString(containerInfo));
97-
}
98-
99101
@Override
100102
public void execute(ScmClient scmClient) throws IOException {
101103
if (!Strings.isNullOrEmpty(replication) && type == null) {
@@ -114,44 +116,104 @@ public void execute(ScmClient scmClient) throws IOException {
114116
.getInt(ScmConfigKeys.OZONE_SCM_CONTAINER_LIST_MAX_COUNT,
115117
ScmConfigKeys.OZONE_SCM_CONTAINER_LIST_MAX_COUNT_DEFAULT);
116118

117-
ContainerListResult containerListAndTotalCount;
119+
// Use SequenceWriter to output JSON array format for all cases
120+
SequenceWriter sequenceWriter = WRITER.writeValues(new NonClosingOutputStream(System.out));
121+
sequenceWriter.init(true); // Initialize as a JSON array
118122

119123
if (!all) {
124+
// Regular listing with count limit
120125
if (count > maxCountAllowed) {
121126
System.err.printf("Attempting to list the first %d records of containers." +
122127
" However it exceeds the cluster's current limit of %d. The results will be capped at the" +
123-
" maximum allowed count.%n", count, ScmConfigKeys.OZONE_SCM_CONTAINER_LIST_MAX_COUNT_DEFAULT);
128+
" maximum allowed count.%n", count, maxCountAllowed);
124129
count = maxCountAllowed;
125130
}
126-
containerListAndTotalCount = scmClient.listContainer(startId, count, state, type, repConfig);
127-
for (ContainerInfo container : containerListAndTotalCount.getContainerInfoList()) {
128-
outputContainerInfo(container);
129-
}
130131

131-
if (containerListAndTotalCount.getTotalCount() > count) {
132+
ContainerListResult containerListResult =
133+
scmClient.listContainer(startId, count, state, type, repConfig);
134+
135+
writeContainers(sequenceWriter, containerListResult.getContainerInfoList());
136+
137+
closeStream(sequenceWriter);
138+
if (containerListResult.getTotalCount() > count) {
132139
System.err.printf("Displaying %d out of %d containers. " +
133-
"Container list has more containers.%n",
134-
count, containerListAndTotalCount.getTotalCount());
140+
"Container list has more containers.%n",
141+
count, containerListResult.getTotalCount());
135142
}
136143
} else {
137-
// Batch size is either count passed through cli or maxCountAllowed
144+
// List all containers by fetching in batches
138145
int batchSize = (count > 0) ? count : maxCountAllowed;
139-
long currentStartId = startId;
140-
int fetchedCount;
141-
142-
do {
143-
// Fetch containers in batches of 'batchSize'
144-
containerListAndTotalCount = scmClient.listContainer(currentStartId, batchSize, state, type, repConfig);
145-
fetchedCount = containerListAndTotalCount.getContainerInfoList().size();
146-
147-
for (ContainerInfo container : containerListAndTotalCount.getContainerInfoList()) {
148-
outputContainerInfo(container);
149-
}
150-
151-
if (fetchedCount > 0) {
152-
currentStartId = containerListAndTotalCount.getContainerInfoList().get(fetchedCount - 1).getContainerID() + 1;
153-
}
154-
} while (fetchedCount > 0);
146+
listAllContainers(scmClient, sequenceWriter, batchSize, repConfig);
147+
closeStream(sequenceWriter);
148+
}
149+
}
150+
151+
private void writeContainers(SequenceWriter writer, List<ContainerInfo> containers)
152+
throws IOException {
153+
for (ContainerInfo container : containers) {
154+
writer.write(container);
155+
}
156+
}
157+
158+
private void closeStream(SequenceWriter writer) throws IOException {
159+
writer.flush();
160+
writer.close();
161+
// Add the final newline
162+
System.out.println();
163+
}
164+
165+
private void listAllContainers(ScmClient scmClient, SequenceWriter writer,
166+
int batchSize, ReplicationConfig repConfig)
167+
throws IOException {
168+
long currentStartId = startId;
169+
int fetchedCount;
170+
171+
do {
172+
ContainerListResult result =
173+
scmClient.listContainer(currentStartId, batchSize, state, type, repConfig);
174+
fetchedCount = result.getContainerInfoList().size();
175+
176+
writeContainers(writer, result.getContainerInfoList());
177+
178+
if (fetchedCount > 0) {
179+
currentStartId =
180+
result.getContainerInfoList().get(fetchedCount - 1).getContainerID() + 1;
181+
}
182+
} while (fetchedCount > 0);
183+
}
184+
185+
186+
private static class NonClosingOutputStream extends OutputStream {
187+
188+
private final OutputStream delegate;
189+
190+
NonClosingOutputStream(OutputStream delegate) {
191+
this.delegate = delegate;
192+
}
193+
194+
@Override
195+
public void write(int b) throws IOException {
196+
delegate.write(b);
197+
}
198+
199+
@Override
200+
public void write(byte[] b) throws IOException {
201+
delegate.write(b);
202+
}
203+
204+
@Override
205+
public void write(byte[] b, int off, int len) throws IOException {
206+
delegate.write(b, off, len);
207+
}
208+
209+
@Override
210+
public void flush() throws IOException {
211+
delegate.flush();
212+
}
213+
214+
@Override
215+
public void close() {
216+
// Ignore close to keep the underlying stream open
155217
}
156218
}
157219
}

hadoop-ozone/dist/src/main/smoketest/admincli/container.robot

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,32 @@ Create container
4444
List containers
4545
${output} = Execute ozone admin container list
4646
Should contain ${output} OPEN
47+
Should Start With ${output} [
48+
Should End With ${output} ]
4749

4850
List containers with explicit host
4951
${output} = Execute ozone admin container list --scm ${SCM}
5052
Should contain ${output} OPEN
53+
Should Start With ${output} [
54+
Should End With ${output} ]
5155

5256
List containers with container state
5357
${output} = Execute ozone admin container list --state=CLOSED
5458
Should Not contain ${output} OPEN
59+
Should Start With ${output} [
60+
Should End With ${output} ]
5561

5662
List containers with replication factor ONE
5763
${output} = Execute ozone admin container list -t RATIS -r ONE
5864
Should Not contain ${output} THREE
65+
Should Start With ${output} [
66+
Should End With ${output} ]
5967

6068
List containers with replication factor THREE
6169
${output} = Execute ozone admin container list -t RATIS -r THREE
6270
Should Not contain ${output} ONE
71+
Should Start With ${output} [
72+
Should End With ${output} ]
6373

6474
Container info
6575
${output} = Execute ozone admin container info "${CONTAINER}"
@@ -87,17 +97,54 @@ Report containers as JSON
8797
List all containers
8898
${output} = Execute ozone admin container list --all
8999
Should contain ${output} OPEN
100+
Should Start With ${output} [
101+
Should End With ${output} ]
90102

91103
List all containers according to count (batchSize)
92104
${output} = Execute ozone admin container list --all --count 10
93105
Should contain ${output} OPEN
106+
Should Start With ${output} [
107+
Should End With ${output} ]
94108

95109
List all containers from a particular container ID
96-
${output} = Execute ozone admin container list --all --start 1
110+
${output} = Execute ozone admin container list --all --start 2
97111
Should contain ${output} OPEN
112+
Should Start With ${output} [
113+
Should End With ${output} ]
114+
115+
Check JSON array parsing
116+
${output} = Execute ozone admin container list
117+
Should Start With ${output} [
118+
Should Contain ${output} containerID
119+
Should End With ${output} ]
120+
${containerIDs} = Execute echo '${output}' | jq -r '.[].containerID'
121+
Should Not Be Empty ${containerIDs}
122+
123+
Check state filtering with JSON array format
124+
${output} = Execute ozone admin container list --state=OPEN
125+
Should Start With ${output} [
126+
Should End With ${output} ]
127+
${states} = Execute echo '${output}' | jq -r '.[].state'
128+
Should Contain ${states} OPEN
129+
Should Not Contain ${states} CLOSED
130+
131+
Check count limit with JSON array format
132+
${output} = Execute ozone admin container create
133+
Should contain ${output} is created
134+
${output} = Execute ozone admin container create
135+
Should contain ${output} is created
136+
${output} = Execute ozone admin container create
137+
Should contain ${output} is created
138+
${output} = Execute ozone admin container create
139+
Should contain ${output} is created
140+
${output} = Execute ozone admin container create
141+
Should contain ${output} is created
142+
${output} = Execute And Ignore Error ozone admin container list --count 5 2> /dev/null # This logs to error that the list is incomplete
143+
${count} = Execute echo '${output}' | jq -r 'length'
144+
Should Be True ${count} == 5
98145

99146
Close container
100-
${container} = Execute ozone admin container list --state OPEN | jq -r 'select(.replicationConfig.replicationFactor == "THREE") | .containerID' | head -1
147+
${container} = Execute ozone admin container list --state OPEN | jq -r '.[] | select(.replicationConfig.replicationFactor == "ONE") | .containerID' | head -1
101148
Execute ozone admin container close "${container}"
102149
${output} = Execute ozone admin container info "${container}"
103150
Should contain ${output} CLOS

hadoop-ozone/dist/src/main/smoketest/balancer/testBalancer.robot

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ Get Uuid
134134

135135
Close All Containers
136136
FOR ${INDEX} IN RANGE 15
137-
${container} = Execute ozone admin container list --state OPEN | jq -r 'select(.replicationConfig.data == 3) | .containerID' | head -1
137+
${container} = Execute ozone admin container list --state OPEN | jq -r '.[] | select(.replicationConfig.data == 3) | .containerID' | head -1
138138
EXIT FOR LOOP IF "${container}" == "${EMPTY}"
139139
${message} = Execute And Ignore Error ozone admin container close "${container}"
140140
Run Keyword If '${message}' != '${EMPTY}' Should Contain ${message} is in closing state
@@ -145,7 +145,7 @@ Close All Containers
145145

146146
All container is closed
147147
${output} = Execute ozone admin container list --state OPEN
148-
Should Be Empty ${output}
148+
Should Be Equal ${output} [ ]
149149

150150
Get Datanode Ozone Used Bytes Info
151151
[arguments] ${uuid}
@@ -186,4 +186,4 @@ Verify Container Balancer for RATIS/EC containers
186186
#We need to ensure that after balancing, the amount of data recorded on each datanode falls within the following ranges:
187187
#{SIZE}*3 < used < {SIZE}*3.5 for RATIS containers, and {SIZE}*0.7 < used < {SIZE}*1.5 for EC containers.
188188
Should Be True ${datanodeOzoneUsedBytesInfoAfterContainerBalancing} < ${SIZE} * ${UPPER_LIMIT}
189-
Should Be True ${datanodeOzoneUsedBytesInfoAfterContainerBalancing} > ${SIZE} * ${LOWER_LIMIT}
189+
Should Be True ${datanodeOzoneUsedBytesInfoAfterContainerBalancing} > ${SIZE} * ${LOWER_LIMIT}

hadoop-ozone/dist/src/main/smoketest/freon/echoRPCLoad.robot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ ${n} 1
2525
*** Test Cases ***
2626
Get Container ID
2727
${result} = Execute ozone admin container create
28-
${containerID} = Execute ozone admin container list --count 1 --state=OPEN | grep -o '"containerID" *: *[^,}]*' | awk -F'[:,]' '{print $2}' | tr -d '" '
28+
${containerID} = Execute ozone admin container list --count 1 --state=OPEN | jq -r '.[0].containerID'
2929
Set Suite Variable ${containerID}
3030

3131
[Read] Ozone DataNode Echo RPC Load Generator with request payload and response payload

hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,8 @@ public void testOzoneAdminCmdListAllContainer()
961961
execute(ozoneAdminShell, args1);
962962
//results will be capped at the maximum allowed count
963963
assertEquals(1, getNumOfContainers());
964-
964+
out.reset();
965+
err.reset();
965966
String[] args2 = new String[] {"container", "list", "-a", "--scm",
966967
"localhost:" + cluster.getStorageContainerManager().getClientRpcPort()};
967968
execute(ozoneAdminShell, args2);

0 commit comments

Comments
 (0)