This is an automated email from the ASF dual-hosted git repository.

smengcl 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 a1ce3b9d900 HDDS-15774. Make SCM Ratis roles string parsing IPv6-safe 
(#10831)
a1ce3b9d900 is described below

commit a1ce3b9d9006d4790bf72d9751d5351938626ae2
Author: Aditya Chechani <[email protected]>
AuthorDate: Wed Aug 12 05:33:32 2026 +0530

    HDDS-15774. Make SCM Ratis roles string parsing IPv6-safe (#10831)
---
 .../java/org/apache/hadoop/hdds/HddsUtils.java     | 63 +++++++++++++++++++++
 .../apache/hadoop/hdds/scm/net/HostAndPort.java    |  3 +-
 .../java/org/apache/hadoop/hdds/TestHddsUtils.java | 62 +++++++++++++++++++++
 .../hadoop/hdds/scm/net/TestHostAndPort.java       |  6 ++
 .../hadoop/hdds/scm/ha/SCMRatisServerImpl.java     | 31 ++++++++---
 .../hdds/scm/server/StorageContainerManager.java   |  2 +-
 .../hadoop/hdds/scm/ha/TestSCMRatisServerImpl.java | 62 +++++++++++++++++++++
 .../hdds/scm/cli/SafeModeCheckSubcommand.java      | 49 +++++++++-------
 .../admin/scm/GetScmRatisRolesSubcommand.java      | 51 ++++++++++-------
 .../ozone/scm/TestGetScmRatisRolesSubcommand.java  | 65 ++++++++++++++++++++++
 .../impl/StorageContainerServiceProviderImpl.java  |  3 +-
 11 files changed, 347 insertions(+), 50 deletions(-)

diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
index 3e6715b11c3..3b29663fedd 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsUtils.java
@@ -243,6 +243,69 @@ public static String getHostPortString(String host, int 
port) {
     return HostAndPort.fromParts(host, port).toString();
   }
 
+  /**
+   * Parse a Ratis role string produced by
+   * {@code SCMRatisServerImpl.getRatisRoles()} into its constituent fields.
+   * The format is {@code [host]:port:ROLE:id:hostIP} where host and hostIP
+   * may be bracketed IPv6 literals.
+   *
+   * @param roleString the encoded role string
+   * @return a 5-element array: {host, port, role, id, hostIP}
+   */
+  public static String[] parseRatisRoleString(String roleString) {
+    Preconditions.checkArgument(roleString != null && !roleString.isEmpty(),
+        "Role string must not be null or empty");
+
+    // Parse from the right: the last field is hostIP (possibly bracketed),
+    // then id (uuid, no colons), then role (LEADER/FOLLOWER, no colons),
+    // and the remainder is host:port (which may be bracketed IPv6).
+    int idx = roleString.length();
+
+    // Field 5: hostIP — may be bracketed IPv6 like [2001:db8::1]
+    String hostIp;
+    if (roleString.charAt(idx - 1) == ']') {
+      int bracket = roleString.lastIndexOf('[');
+      Preconditions.checkArgument(bracket > 0,
+          "Malformed role string (unmatched bracket): %s", roleString);
+      hostIp = roleString.substring(bracket + 1, idx - 1);
+      Preconditions.checkArgument(roleString.charAt(bracket - 1) == ':',
+          "Malformed role string (expected ':' before '['): %s", roleString);
+      idx = bracket - 1;
+    } else {
+      int sep = roleString.lastIndexOf(':');
+      Preconditions.checkArgument(sep > 0,
+          "Malformed role string (expected host:port:role:id:hostIP): %s", 
roleString);
+      hostIp = roleString.substring(sep + 1);
+      idx = sep;
+    }
+
+    // Field 4: id (uuid or peer id, no colons)
+    int sep3 = roleString.lastIndexOf(':', idx - 1);
+    Preconditions.checkArgument(sep3 > 0,
+        "Malformed role string (cannot find id field): %s", roleString);
+    String id = roleString.substring(sep3 + 1, idx);
+    idx = sep3;
+
+    // Field 3: role (LEADER/FOLLOWER, no colons)
+    int sep2 = roleString.lastIndexOf(':', idx - 1);
+    Preconditions.checkArgument(sep2 > 0,
+        "Malformed role string (cannot find role field): %s", roleString);
+    String role = roleString.substring(sep2 + 1, idx);
+    idx = sep2;
+
+    // Remainder is host:port — use HostAndPort to parse safely
+    String hostPort = roleString.substring(0, idx);
+    Preconditions.checkArgument(!hostPort.isEmpty(),
+        "Malformed role string (empty host:port): %s", roleString);
+    HostAndPort hp = HostAndPort.fromString(hostPort);
+    Preconditions.checkArgument(hp.hasPort(),
+        "Malformed role string (missing port): %s", roleString);
+    String host = hp.getHost();
+    String port = String.valueOf(hp.getPort());
+
+    return new String[]{host, port, role, id, hostIp};
+  }
+
   /**
    * Retrieve a number, trying the supplied config keys in order.
    * Each config value may be absent
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/net/HostAndPort.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/net/HostAndPort.java
index 2a142ea8a11..75400ff707e 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/net/HostAndPort.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/net/HostAndPort.java
@@ -20,6 +20,7 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.util.Objects;
+import org.apache.hadoop.hdds.HddsUtils;
 import org.apache.hadoop.net.NetUtils;
 
 /**
@@ -37,7 +38,7 @@ public class HostAndPort {
   public HostAndPort(String host, int port) {
     this.host = host;
     this.port = port;
-    this.hostAndPortString = host + ":" + port;
+    this.hostAndPortString = HddsUtils.getHostPortString(host, port);
     this.hash = host.hashCode() ^ Integer.hashCode(port);
     this.address = NetUtils.createSocketAddr(hostAndPortString);
   }
diff --git 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHddsUtils.java 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHddsUtils.java
index 36bc8bed95f..8fdf6de7b50 100644
--- a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHddsUtils.java
+++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/TestHddsUtils.java
@@ -102,6 +102,68 @@ void testGetHostPortString() {
     assertEquals("[2001:db8::1]:9858", 
HddsUtils.getHostPortString("[2001:db8::1]", 9858));
   }
 
+  @Test
+  void testParseRatisRoleStringIPv4() {
+    String input = "hostname1:9894:LEADER:peer-uuid-123:192.168.1.1";
+    String[] result = HddsUtils.parseRatisRoleString(input);
+    assertEquals("hostname1", result[0]);
+    assertEquals("9894", result[1]);
+    assertEquals("LEADER", result[2]);
+    assertEquals("peer-uuid-123", result[3]);
+    assertEquals("192.168.1.1", result[4]);
+  }
+
+  @Test
+  void testParseRatisRoleStringIPv6() {
+    String input = "[2001:db8::1]:9894:LEADER:peer1:[2001:db8:0:0:0:0:0:1]";
+    String[] result = HddsUtils.parseRatisRoleString(input);
+    assertEquals("2001:db8::1", result[0]);
+    assertEquals("9894", result[1]);
+    assertEquals("LEADER", result[2]);
+    assertEquals("peer1", result[3]);
+    assertEquals("2001:db8:0:0:0:0:0:1", result[4]);
+  }
+
+  @Test
+  void testParseRatisRoleStringIPv6Follower() {
+    String input = "[::1]:9894:FOLLOWER:abc-def:[0:0:0:0:0:0:0:1]";
+    String[] result = HddsUtils.parseRatisRoleString(input);
+    assertEquals("::1", result[0]);
+    assertEquals("9894", result[1]);
+    assertEquals("FOLLOWER", result[2]);
+    assertEquals("abc-def", result[3]);
+    assertEquals("0:0:0:0:0:0:0:1", result[4]);
+  }
+
+  @Test
+  void testParseRatisRoleStringEmptyHostIp() {
+    String input = "scm-host:9894:LEADER:uuid123:";
+    String[] result = HddsUtils.parseRatisRoleString(input);
+    assertEquals("scm-host", result[0]);
+    assertEquals("9894", result[1]);
+    assertEquals("LEADER", result[2]);
+    assertEquals("uuid123", result[3]);
+    assertEquals("", result[4]);
+  }
+
+  @Test
+  void testParseRatisRoleStringRejectsNull() {
+    assertThrows(IllegalArgumentException.class,
+        () -> HddsUtils.parseRatisRoleString(null));
+  }
+
+  @Test
+  void testParseRatisRoleStringRejectsEmpty() {
+    assertThrows(IllegalArgumentException.class,
+        () -> HddsUtils.parseRatisRoleString(""));
+  }
+
+  @Test
+  void testParseRatisRoleStringRejectsTooFewFields() {
+    assertThrows(IllegalArgumentException.class,
+        () -> HddsUtils.parseRatisRoleString("host:9894"));
+  }
+
   static List<Arguments> validPaths() {
     return Arrays.asList(
         Arguments.of("/", "/"),
diff --git 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/net/TestHostAndPort.java
 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/net/TestHostAndPort.java
index 9be015f5ef9..134e89aa34e 100644
--- 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/net/TestHostAndPort.java
+++ 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/scm/net/TestHostAndPort.java
@@ -42,6 +42,12 @@ public void setAddressRejectsNull() {
     assertThrows(NullPointerException.class, () -> address.setAddress(null));
   }
 
+  @Test
+  public void ipv6HostAndPortString() {
+    HostAndPort address = new HostAndPort("2001:db8::1", 9894);
+    assertEquals("[2001:db8::1]:9894", address.getHostAndPortString());
+  }
+
   @Test
   public void setAddressDoesNotChangeIdentity() throws Exception {
     HostAndPort address = new HostAndPort("127.0.0.1", 9861);
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMRatisServerImpl.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMRatisServerImpl.java
index ac9564ca95d..94b381ccdd9 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMRatisServerImpl.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMRatisServerImpl.java
@@ -21,6 +21,7 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
+import com.google.common.net.InetAddresses;
 import jakarta.annotation.Nullable;
 import java.io.IOException;
 import java.net.InetAddress;
@@ -28,6 +29,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.OptionalInt;
 import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
@@ -289,13 +291,28 @@ public List<String> getRatisRoles() {
         LOG.error("SCM Ratis PeerInetAddress {} is unresolvable",
             peer.getAddress());
       }
-      ratisRoles.add((peer.getAddress() == null ? "" :
-          peer.getAddress().concat(peer.equals(leader) ?
-                  ":".concat(RaftProtos.RaftPeerRole.LEADER.toString()) :
-                  ":".concat(RaftProtos.RaftPeerRole.FOLLOWER.toString()))
-                  .concat(":".concat(peer.getId().toString()))
-                  .concat(":".concat(peerInetAddress == null ? "" :
-                      peerInetAddress.getHostAddress()))));
+      if (peer.getAddress() == null) {
+        ratisRoles.add("");
+        continue;
+      }
+      String host = HddsUtils.getHostName(peer.getAddress()).orElse("");
+      OptionalInt portOpt = HddsUtils.getHostPort(peer.getAddress());
+      if (!portOpt.isPresent()) {
+        LOG.error("SCM Ratis peer address {} has no port, skipping",
+            peer.getAddress());
+        continue;
+      }
+      String normalizedAddress = HddsUtils.getHostPortString(host, 
portOpt.getAsInt());
+      String role = peer.equals(leader)
+          ? RaftProtos.RaftPeerRole.LEADER.toString()
+          : RaftProtos.RaftPeerRole.FOLLOWER.toString();
+      String hostIp = "";
+      if (peerInetAddress != null) {
+        hostIp = InetAddresses.toUriString(peerInetAddress);
+      }
+      String roleEntry = normalizedAddress + ":" + role + ":"
+          + peer.getId().toString() + ":" + hostIp;
+      ratisRoles.add(roleEntry);
     }
     return ratisRoles;
   }
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
index 100c9feb9b6..93df74a6ea7 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java
@@ -2190,7 +2190,7 @@ public List<List<String>> getScmRatisRoles() {
       List<String> ratisRoles = server.getRatisRoles();
       List<List<String>> result = new ArrayList<>();
       for (String role : ratisRoles) {
-        String[] roleArr = role.split(":");
+        String[] roleArr = HddsUtils.parseRatisRoleString(role);
         List<String> scmInfo = new ArrayList<>();
         // Host Name
         scmInfo.add(roleArr[0]);
diff --git 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMRatisServerImpl.java
 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMRatisServerImpl.java
index 6b601868c32..56b99fb8f65 100644
--- 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMRatisServerImpl.java
+++ 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMRatisServerImpl.java
@@ -19,6 +19,7 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -27,11 +28,15 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import java.util.List;
 import java.util.UUID;
+import org.apache.hadoop.hdds.HddsUtils;
 import org.apache.hadoop.hdds.conf.ConfigurationSource;
 import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
 import org.apache.hadoop.hdds.security.SecurityConfig;
 import org.apache.ratis.conf.RaftProperties;
+import org.apache.ratis.protocol.RaftGroup;
+import org.apache.ratis.protocol.RaftGroupId;
 import org.apache.ratis.protocol.RaftPeer;
 import org.apache.ratis.protocol.RaftPeerId;
 import org.apache.ratis.server.RaftServer;
@@ -103,4 +108,61 @@ public void  testGetLeaderId() throws Exception {
     }
   }
 
+  @Test
+  public void testGetRatisRolesWithIPv6() throws Exception {
+    try (
+        MockedConstruction<SecurityConfig> mockedSecurityConfigConstruction = 
mockConstruction(SecurityConfig.class);
+        MockedStatic<RaftServer> staticMockedRaftServer = 
mockStatic(RaftServer.class);
+        MockedStatic<RatisUtil> staticMockedRatisUtil = 
mockStatic(RatisUtil.class);
+    ) {
+      ConfigurationSource conf = mock(ConfigurationSource.class);
+      StorageContainerManager scm = mock(StorageContainerManager.class);
+      when(scm.getClusterId()).thenReturn("CID-" + UUID.randomUUID());
+      SCMHADBTransactionBuffer dbTransactionBuffer = 
mock(SCMHADBTransactionBuffer.class);
+
+      RaftServer.Builder raftServerBuilder = mock(RaftServer.Builder.class);
+      when(raftServerBuilder.setServerId(any())).thenReturn(raftServerBuilder);
+      
when(raftServerBuilder.setProperties(any())).thenReturn(raftServerBuilder);
+      
when(raftServerBuilder.setStateMachineRegistry(any())).thenReturn(raftServerBuilder);
+      when(raftServerBuilder.setOption(any())).thenReturn(raftServerBuilder);
+      when(raftServerBuilder.setGroup(any())).thenReturn(raftServerBuilder);
+      
when(raftServerBuilder.setParameters(any())).thenReturn(raftServerBuilder);
+
+      RaftServer raftServer = mock(RaftServer.class);
+      RaftServer.Division division = mock(RaftServer.Division.class);
+      when(raftServer.getDivision(any())).thenReturn(division);
+      when(raftServerBuilder.build()).thenReturn(raftServer);
+      
staticMockedRaftServer.when(RaftServer::newBuilder).thenReturn(raftServerBuilder);
+
+      RaftProperties raftProperties = mock(RaftProperties.class);
+      staticMockedRatisUtil.when(() -> 
RatisUtil.newRaftProperties(conf)).thenReturn(raftProperties);
+
+      SecurityConfig sc = new SecurityConfig(conf);
+      when(sc.isSecurityEnabled()).thenReturn(false);
+
+      SCMRatisServerImpl scmRatisServer = spy(new SCMRatisServerImpl(conf, 
scm, dbTransactionBuffer));
+
+      // IPv6 peer address in the bracketed format that getRatisHostPortStr() 
produces
+      RaftPeer ipv6Peer = RaftPeer.newBuilder()
+          .setId(RaftPeerId.valueOf("peer1"))
+          .setAddress("[2001:db8::1]:9894")
+          .build();
+
+      RaftGroup raftGroup = RaftGroup.valueOf(RaftGroupId.randomId(), 
ipv6Peer);
+      when(division.getGroup()).thenReturn(raftGroup);
+      doReturn(ipv6Peer).when(scmRatisServer).getLeader();
+
+      List<String> roles = scmRatisServer.getRatisRoles();
+      assertEquals(1, roles.size());
+
+      String roleString = roles.get(0);
+      String[] parsed = HddsUtils.parseRatisRoleString(roleString);
+      assertEquals("2001:db8::1", parsed[0]);
+      assertEquals("9894", parsed[1]);
+      assertEquals("LEADER", parsed[2]);
+      assertEquals("peer1", parsed[3]);
+      assertTrue(!parsed[4].isEmpty(), "hostIP should be resolved");
+    }
+  }
+
 }
diff --git 
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/SafeModeCheckSubcommand.java
 
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/SafeModeCheckSubcommand.java
index 1fb97d6e01a..7af5ea3d0c6 100644
--- 
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/SafeModeCheckSubcommand.java
+++ 
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/hdds/scm/cli/SafeModeCheckSubcommand.java
@@ -18,8 +18,10 @@
 package org.apache.hadoop.hdds.scm.cli;
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.util.List;
 import java.util.Map;
+import java.util.OptionalInt;
 import java.util.concurrent.Callable;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
@@ -104,16 +106,21 @@ private SCMNodeInfo findLeaderNode(ScmClient scmClient) 
throws IOException {
     try {
       List<String> roles = scmClient.getScmRoles();
       for (String role : roles) {
-        String[] parts = role.split(":");
-        if (parts.length < 3 || !"LEADER".equalsIgnoreCase(parts[2])) {
+        String[] parts;
+        try {
+          parts = HddsUtils.parseRatisRoleString(role);
+        } catch (IllegalArgumentException e) {
+          continue;
+        }
+        if (!"LEADER".equalsIgnoreCase(parts[2])) {
           continue;
         }
         String leaderHost = parts[0];
-        String leaderIp = parts.length >= 5 ? parts[4] : null;
+        String leaderIp = parts[4];
         for (SCMNodeInfo node : nodes) {
-          String nodeHost = node.getScmClientAddress().split(":")[0];
+          String nodeHost = 
HddsUtils.getHostName(node.getScmClientAddress()).orElse("");
 
-          if (matchesAddress(leaderHost, nodeHost) || (leaderIp != null && 
!leaderIp.isEmpty() &&
+          if (matchesAddress(leaderHost, nodeHost) || (!leaderIp.isEmpty() &&
                   matchesAddress(leaderIp, nodeHost))) {
             return node;
           }
@@ -176,8 +183,9 @@ private void queryNode(ScmClient scmClient, ScmNodeTarget 
targetScmNode, SCMNode
   }
 
   /**
-   * Check if the given SCMNodeInfo matches the target address.
-   * Tries to match by direct string comparison and by resolved address.
+   * Check if the given addresses match by comparing host portions and ports.
+   * Inputs may be bare hosts or host:port strings. Handles IPv6 equivalence
+   * (e.g. 2001:db8::1 vs 2001:db8:0:0:0:0:0:1) by resolving to InetAddress.
    */
   private boolean matchesAddress(String address1, String address2) {
     if (address1.equalsIgnoreCase(address2)) {
@@ -185,23 +193,24 @@ private boolean matchesAddress(String address1, String 
address2) {
     }
 
     try {
-      // Parse both addresses into host:port components
-      String[] parts1 = address1.split(":", 2);
-      String[] parts2 = address2.split(":", 2);
-
-      String host1 = parts1[0];
-      String host2 = parts2[0];
-      
-      // Hostnames must match
-      if (!host1.equalsIgnoreCase(host2)) {
+      String host1 = HddsUtils.getHostName(address1).orElse(address1);
+      String host2 = HddsUtils.getHostName(address2).orElse(address2);
+
+      boolean hostsMatch = host1.equalsIgnoreCase(host2);
+      if (!hostsMatch) {
+        InetAddress inet1 = InetAddress.getByName(host1);
+        InetAddress inet2 = InetAddress.getByName(host2);
+        hostsMatch = inet1.equals(inet2);
+      }
+      if (!hostsMatch) {
         return false;
       }
 
-      // If both have ports specified, they must match
-      if (parts1.length > 1 && parts2.length > 1) {
-        return parts1[1].equals(parts2[1]);
+      OptionalInt port1 = HddsUtils.getHostPort(address1);
+      OptionalInt port2 = HddsUtils.getHostPort(address2);
+      if (port1.isPresent() && port2.isPresent()) {
+        return port1.getAsInt() == port2.getAsInt();
       }
-
       return true;
     } catch (Exception e) {
       // If address resolution fails, no match
diff --git 
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/GetScmRatisRolesSubcommand.java
 
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/GetScmRatisRolesSubcommand.java
index da3076306d7..14021c4261e 100644
--- 
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/GetScmRatisRolesSubcommand.java
+++ 
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/GetScmRatisRolesSubcommand.java
@@ -17,14 +17,13 @@
 
 package org.apache.hadoop.ozone.admin.scm;
 
-import static java.lang.System.err;
-
+import com.google.common.net.HostAndPort;
 import java.io.IOException;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import org.apache.hadoop.hdds.HddsUtils;
 import org.apache.hadoop.hdds.cli.HddsVersionProvider;
 import org.apache.hadoop.hdds.scm.cli.ScmSubcommand;
 import org.apache.hadoop.hdds.scm.client.ScmClient;
@@ -73,11 +72,18 @@ public void execute(ScmClient scmClient) throws IOException 
{
       formattingCLIUtils.addHeaders(SCM_ROLES_HEADER);
 
       for (String role : peerRoles) {
-        String[] roleItems = role.split(":");
-        if (roleItems.length < 2) {
-          err.println("Invalid response received for ScmRatisRoles.");
+        try {
+          String[] roleItems = HddsUtils.parseRatisRoleString(role);
+          formattingCLIUtils.addLine(roleItems);
+        } catch (IllegalArgumentException e) {
+          try {
+            HostAndPort hp = HostAndPort.fromString(role);
+            String port = hp.hasPort() ? String.valueOf(hp.getPort()) : "";
+            formattingCLIUtils.addLine(new String[]{hp.getHost(), port, "", 
"", ""});
+          } catch (IllegalArgumentException ex) {
+            formattingCLIUtils.addLine(new String[]{role, "", "", "", ""});
+          }
         }
-        formattingCLIUtils.addLine(roleItems);
       }
       System.out.println(formattingCLIUtils.render());
     } else {
@@ -92,20 +98,25 @@ private Map<String, Map<String, String>> parseScmRoles(
     Map<String, Map<String, String>> allRoles = new HashMap<>();
     for (String role : peerRoles) {
       Map<String, String> roleDetails = new HashMap<>();
-      String[] roles = role.split(":");
-      if (roles.length < 2) {
-        err.println("Invalid response received for ScmRatisRoles.");
-        return Collections.emptyMap();
-      }
-      // In case, there is no ratis, there is no ratis role.
-      // This will just print the hostname with ratis port as the address
-      roleDetails.put("address", roles[0].concat(":").concat(roles[1]));
-      if (roles.length == 5) {
-        roleDetails.put("raftPeerRole", roles[2]);
-        roleDetails.put("ID", roles[3]);
-        roleDetails.put("InetAddress", roles[4]);
+      try {
+        String[] fields = HddsUtils.parseRatisRoleString(role);
+        roleDetails.put("address", HddsUtils.getHostPortString(
+            fields[0], Integer.parseInt(fields[1])));
+        roleDetails.put("raftPeerRole", fields[2]);
+        roleDetails.put("ID", fields[3]);
+        roleDetails.put("InetAddress", fields[4]);
+        allRoles.put(fields[0], roleDetails);
+      } catch (IllegalArgumentException e) {
+        try {
+          HostAndPort hp = HostAndPort.fromString(role);
+          String addr = hp.hasPort() ? 
HddsUtils.getHostPortString(hp.getHost(), hp.getPort()) : hp.getHost();
+          roleDetails.put("address", addr);
+          allRoles.put(hp.getHost(), roleDetails);
+        } catch (IllegalArgumentException ex) {
+          roleDetails.put("address", role);
+          allRoles.put(role, roleDetails);
+        }
       }
-      allRoles.put(roles[0], roleDetails);
     }
     return allRoles;
   }
diff --git 
a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/scm/TestGetScmRatisRolesSubcommand.java
 
b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/scm/TestGetScmRatisRolesSubcommand.java
index c622a1ff3d1..52c4a4cda2e 100644
--- 
a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/scm/TestGetScmRatisRolesSubcommand.java
+++ 
b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/scm/TestGetScmRatisRolesSubcommand.java
@@ -24,6 +24,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import org.apache.hadoop.hdds.scm.client.ScmClient;
+import org.apache.hadoop.hdds.server.JsonUtils;
 import org.apache.hadoop.ozone.admin.scm.GetScmRatisRolesSubcommand;
 import org.apache.ozone.test.GenericTestUtils;
 import org.junit.jupiter.api.Test;
@@ -61,4 +62,68 @@ public void testGetScmHARatisRoles() throws Exception {
     }
   }
 
+  @Test
+  public void testGetScmRolesNonRatisShortStringTable() 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("host:9894");
+
+    when(client.getScmRoles()).thenAnswer(invocation -> result);
+
+    try (GenericTestUtils.SystemOutCapturer capture =
+        new GenericTestUtils.SystemOutCapturer()) {
+      cmd.execute(client);
+      assertThat(capture.getOutput())
+          .containsPattern("\\|\\s+host\\s+\\|\\s+9894\\s+\\|");
+    }
+  }
+
+  @Test
+  public void testGetScmRolesNonRatisShortStringJson() throws Exception {
+    GetScmRatisRolesSubcommand cmd = new GetScmRatisRolesSubcommand();
+    ScmClient client = mock(ScmClient.class);
+    CommandLine c = new CommandLine(cmd);
+    c.parseArgs("--json");
+
+    List<String> result = new ArrayList<>();
+    result.add("host:9894");
+
+    when(client.getScmRoles()).thenAnswer(invocation -> result);
+
+    try (GenericTestUtils.SystemOutCapturer capture =
+        new GenericTestUtils.SystemOutCapturer()) {
+      cmd.execute(client);
+      assertThat(JsonUtils.readTree(capture.getOutput())).isEqualTo(
+          JsonUtils.readTree("{\"host\":{\"address\":\"host:9894\"}}"));
+    }
+  }
+
+  @Test
+  public void testGetScmHARatisRolesIPv6() 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("[2001:db8::1]:9894:LEADER:e428ca07-b2a3-4756-bf9b-a4abb033c7d1:[2001:db8:0:0:0:0:0:1]");
+    
result.add("[2001:db8::2]:9894:FOLLOWER:61b1c8e5-da40-4567-8a17-96a0234ba14e:[2001:db8:0:0:0:0:0:2]");
+
+    when(client.getScmRoles()).thenAnswer(invocation -> result);
+
+    try (GenericTestUtils.SystemOutCapturer capture =
+        new GenericTestUtils.SystemOutCapturer()) {
+      cmd.execute(client);
+      assertThat(capture.getOutput()).contains("2001:db8::1");
+      assertThat(capture.getOutput()).contains("9894");
+      assertThat(capture.getOutput()).contains("LEADER");
+      
assertThat(capture.getOutput()).contains("e428ca07-b2a3-4756-bf9b-a4abb033c7d1");
+      assertThat(capture.getOutput()).contains("FOLLOWER");
+    }
+  }
+
 }
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/spi/impl/StorageContainerServiceProviderImpl.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/spi/impl/StorageContainerServiceProviderImpl.java
index 96aca5feed7..be3d917f42c 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/spi/impl/StorageContainerServiceProviderImpl.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/spi/impl/StorageContainerServiceProviderImpl.java
@@ -31,6 +31,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import javax.inject.Inject;
 import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.hdds.HddsUtils;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
 import 
org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB;
@@ -138,7 +139,7 @@ public DBCheckpoint getSCMDBSnapshot() {
       try {
         List<String> ratisRoles = scmClient.getScmInfo().getPeerRoles();
         for (String ratisRole : ratisRoles) {
-          String[] role = ratisRole.split(":");
+          String[] role = HddsUtils.parseRatisRoleString(ratisRole);
           if (role[2].equals(RaftProtos.RaftPeerRole.LEADER.toString())) {
             String hostAddress = role[4].trim();
             int grpcPort = configuration.getInt(


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to