diff --git a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/ListInfoSubcommand.java b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/ListInfoSubcommand.java index c7b65d5ba2a3..51ec37cca4d4 100644 --- a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/ListInfoSubcommand.java +++ b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/datanode/ListInfoSubcommand.java @@ -19,6 +19,7 @@ import com.google.common.base.Strings; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -87,7 +88,13 @@ public void execute(ScmClient scmClient) throws IOException { .getFromProtoBuf(node.getNodeID()), node.getNodeOperationalStates(0), node.getNodeStates(0)); - printDatanodeInfo(dwa); + + if (json) { + List singleList = Collections.singletonList(dwa); + System.out.println(JsonUtils.toJsonStringWithDefaultPrettyPrinter(singleList)); + } else { + printDatanodeInfo(dwa); + } return; } Stream allNodes = getAllNodes(scmClient).stream(); diff --git a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestListInfoSubcommand.java b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestListInfoSubcommand.java index 239d8f6ae81d..38763ff3ae98 100644 --- a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestListInfoSubcommand.java +++ b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/hdds/scm/cli/datanode/TestListInfoSubcommand.java @@ -17,16 +17,22 @@ package org.apache.hadoop.hdds.scm.cli.datanode; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.regex.Matcher; @@ -49,6 +55,7 @@ public class TestListInfoSubcommand { private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); private final PrintStream originalOut = System.out; private final PrintStream originalErr = System.err; + private final ObjectMapper mapper = new ObjectMapper(); private static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name(); @BeforeEach @@ -99,6 +106,54 @@ public void testDataNodeOperationalStateAndHealthIncludedInOutput() m = p.matcher(outContent.toString(DEFAULT_ENCODING)); assertTrue(m.find()); + + // ----- with JSON flag ----- + outContent.reset(); + + CommandLine cmdWithJsonFlag = new CommandLine(cmd); + cmdWithJsonFlag.parseArgs("--json"); + cmd.execute(scmClient); + + String jsonOutput = outContent.toString(DEFAULT_ENCODING); + JsonNode root; + try { + root = mapper.readTree(jsonOutput); + } catch (IOException e) { + fail("Invalid JSON output:\n" + jsonOutput + "\nError: " + e.getMessage()); + return; + } + + assertTrue(root.isArray(), "JSON output should be an array"); + assertEquals(4, root.size(), "Expected 4 nodes in JSON output"); + + List healthStates = new ArrayList<>(); + List operationalStates = new ArrayList<>(); + for (JsonNode node : root) { + healthStates.add(node.get("healthState").asText()); + operationalStates.add(node.get("opState").asText()); + } + + // Check expected operational states are present + List expectedOpStates = Arrays.asList("IN_SERVICE", "DECOMMISSIONING"); + for (String expectedState : expectedOpStates) { + assertTrue(operationalStates.contains(expectedState), + "Expected operational state: " + expectedState + " but not found"); + } + + // Check all expected health states are present + for (HddsProtos.NodeState state : HddsProtos.NodeState.values()) { + assertTrue(healthStates.contains(state.toString()), + "Expected health state: " + state + " but not found"); + } + + // Check order: HEALTHY -> STALE -> DEAD -> HEALTHY_READONLY + List expectedOrder = Arrays.asList("HEALTHY", "STALE", "DEAD", "HEALTHY_READONLY"); + int lastIndex = -1; + for (String state : healthStates) { + int index = expectedOrder.indexOf(state); + assertTrue(index >= lastIndex, "Health states not in expected order: " + healthStates); + lastIndex = index; + } } @Test @@ -125,6 +180,34 @@ public void testDataNodeByUuidOutput() Pattern.MULTILINE); m = p.matcher(outContent.toString(DEFAULT_ENCODING)); assertTrue(m.find()); + + outContent.reset(); + + // ----- with JSON flag ----- + CommandLine cmdWithJson = new CommandLine(cmd); + cmdWithJson.parseArgs("--id", nodes.get(0).getNodeID().getUuid(), "--json"); + cmd.execute(scmClient); + + String jsonOutput = outContent.toString(DEFAULT_ENCODING); + JsonNode root; + try { + root = mapper.readTree(jsonOutput); + } catch (IOException e) { + fail("Invalid JSON output:\n" + jsonOutput + "\nError: " + e.getMessage()); + return; + } + + assertTrue(root.isArray(), "JSON output should be an array"); + assertEquals(1, root.size(), "Expected 1 node in JSON output"); + + JsonNode node = root.get(0); + assertTrue(node.has("datanodeDetails"), "Missing datanodeDetails"); + String opState = node.get("opState").asText(); + String uuid = node.get("datanodeDetails").get("uuid").asText(); + + assertEquals("IN_SERVICE", opState, "Expected opState IN_SERVICE but got: " + opState); + assertEquals(nodes.get(0).getNodeID().getUuid(), uuid, + "Expected UUID " + nodes.get(0).getNodeID().getUuid() + " but got: " + uuid); } private List getNodeDetails() {