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

ivandika3 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 521e046314b HDDS-11839. Accept SCM node id in ozone admin scm transfer 
(#11022)
521e046314b is described below

commit 521e046314bc3bef2e66c5c58e0f3d9a8c604fe8
Author: rjgoyln <[email protected]>
AuthorDate: Thu Aug 27 14:22:56 2026 +0800

    HDDS-11839. Accept SCM node id in ozone admin scm transfer (#11022)
---
 hadoop-hdds/docs/content/feature/SCM-HA.md         |  3 +-
 .../hadoop/hdds/scm/ha/SCMHANodeDetails.java       |  7 +++
 .../hdds/scm/server/SCMClientProtocolServer.java   | 24 ++++++++-
 .../hdds/scm/server/StorageContainerManager.java   |  5 +-
 .../admin/scm/TransferScmLeaderSubCommand.java     |  2 +-
 .../ozone/shell/TestTransferLeadershipShell.java   | 60 +++++++++++++++++-----
 6 files changed, 81 insertions(+), 20 deletions(-)

diff --git a/hadoop-hdds/docs/content/feature/SCM-HA.md 
b/hadoop-hdds/docs/content/feature/SCM-HA.md
index 0703ccba2fd..d5d831f8610 100644
--- a/hadoop-hdds/docs/content/feature/SCM-HA.md
+++ b/hadoop-hdds/docs/content/feature/SCM-HA.md
@@ -104,7 +104,7 @@ ozone admin scm transfer -id <SCM_SERVICE_ID> -r
 ```
 
 *   `-id, --service-id`: Specifies the SCM Service ID.
-*   `-n, --new-leader-id`: The SCM UUID (Raft peer ID) of the SCM to which 
leadership will be transferred (e.g., `e6877ce5-56cd-4f0b-ad60-4c8ef9000882`).
+*   `-n, --new-leader-id`: The SCM to which leadership will be transferred, 
given either as the SCM UUID (Raft peer ID, e.g. 
`e6877ce5-56cd-4f0b-ad60-4c8ef9000882`) or as the configured node id (e.g. 
`scm1`).
 *   `-r, --random`: Randomly chooses a follower to transfer leadership to.
 
 ### Example
@@ -113,6 +113,7 @@ To transfer leadership to a specific SCM in a cluster with 
service ID `cluster1`
 
 ```bash
 ozone admin scm transfer -id cluster1 -n e6877ce5-56cd-4f0b-ad60-4c8ef9000882
+ozone admin scm transfer -id cluster1 -n scm1
 ```
 
 To transfer leadership to a random follower:
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHANodeDetails.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHANodeDetails.java
index 9c038ac9ddf..60a4ca7325f 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHANodeDetails.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHANodeDetails.java
@@ -116,6 +116,13 @@ public List< SCMNodeDetails > getPeerNodeDetails() {
     return peerNodeDetails;
   }
 
+  /** Details of every SCM in the service, the local one included. */
+  public List<SCMNodeDetails> getAllNodeDetails() {
+    final List<SCMNodeDetails> allNodes = new ArrayList<>(peerNodeDetails);
+    allNodes.add(localNodeDetails);
+    return allNodes;
+  }
+
   public static SCMHANodeDetails loadDefaultConfig(
       OzoneConfiguration conf) throws IOException {
     int ratisPort = conf.getInt(
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 96b9efce50f..8069971459e 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
@@ -89,6 +89,7 @@
 import org.apache.hadoop.hdds.scm.events.SCMEvents;
 import org.apache.hadoop.hdds.scm.exceptions.SCMException;
 import org.apache.hadoop.hdds.scm.exceptions.SCMException.ResultCodes;
+import org.apache.hadoop.hdds.scm.ha.SCMNodeDetails;
 import org.apache.hadoop.hdds.scm.ha.SCMRatisServer;
 import org.apache.hadoop.hdds.scm.ha.SCMRatisServerImpl;
 import org.apache.hadoop.hdds.scm.node.DatanodeInfo;
@@ -1002,7 +1003,7 @@ public void transferLeadership(String newLeaderId)
             .orElseThrow(() -> new IOException("Cannot" +
                 " find a new leader to transfer leadership."));
       } else {
-        targetPeerId = RaftPeerId.valueOf(newLeaderId);
+        targetPeerId = resolveTargetPeerId(newLeaderId, group);
       }
       final GrpcTlsConfig tlsConfig =
           createSCMRatisTLSConfig(new SecurityConfig(scm.getConfiguration()),
@@ -1019,6 +1020,27 @@ public void transferLeadership(String newLeaderId)
             SCMAction.TRANSFER_LEADERSHIP, auditMap));
   }
 
+  /** Resolves the transfer target, which may be given as an SCM UUID or as a 
configured SCM node id. */
+  private RaftPeerId resolveTargetPeerId(String newLeaderId, RaftGroup group) {
+    final RaftPeerId peerId = RaftPeerId.valueOf(newLeaderId);
+    if (group.getPeer(peerId) != null) {
+      return peerId;
+    }
+    // Raft peer ids are SCM UUIDs, so a node id only matches through its 
configured Ratis address.
+    for (SCMNodeDetails node : scm.getSCMHANodeDetails().getAllNodeDetails()) {
+      if (!newLeaderId.equals(node.getNodeId())) {
+        continue;
+      }
+      for (RaftPeer peer : group.getPeers()) {
+        if (node.getRatisHostPortStr().equals(peer.getAddress())) {
+          return peer.getId();
+        }
+      }
+    }
+    // Leave an unmatched id to RatisHelper, which reports the group it did 
not match.
+    return peerId;
+  }
+
   @Deprecated
   @Override
   public List<DeletedBlocksTransactionInfo> getFailedDeletedBlockTxn(int count,
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 e2e9834607a..12665164c85 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
@@ -2210,10 +2210,7 @@ public String getPrimordialNode() {
     String primordialNode = SCMHAUtils.getPrimordialSCM(configuration);
     // primordialNode can be nodeId too . If it is then return hostname.
     if (HddsUtils.getSCMNodeIds(configuration).contains(primordialNode)) {
-      List<SCMNodeDetails> localAndPeerNodes =
-          new ArrayList<>(scmHANodeDetails.getPeerNodeDetails());
-      localAndPeerNodes.add(getSCMHANodeDetails().getLocalNodeDetails());
-      for (SCMNodeDetails nodes : localAndPeerNodes) {
+      for (SCMNodeDetails nodes : scmHANodeDetails.getAllNodeDetails()) {
         if (nodes.getNodeId().equals(primordialNode)) {
           return nodes.getHostName();
         }
diff --git 
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/TransferScmLeaderSubCommand.java
 
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/TransferScmLeaderSubCommand.java
index 70f30e3d519..6cdc9a7e3ea 100644
--- 
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/TransferScmLeaderSubCommand.java
+++ 
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/scm/TransferScmLeaderSubCommand.java
@@ -48,7 +48,7 @@ static class TransferOption {
     @CommandLine.Option(
         names = {"-n", "--new-leader-id"},
         description = "The new leader id of SCM to transfer leadership. " +
-            "Should be ScmId(UUID)."
+            "Either the ScmId(UUID) or the configured node id (e.g. scm1)."
     )
     private String scmId;
 
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestTransferLeadershipShell.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestTransferLeadershipShell.java
index b187e3179fc..e866842ef22 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestTransferLeadershipShell.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestTransferLeadershipShell.java
@@ -19,12 +19,15 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNotSame;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Stream;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.ratis.RatisHelper;
 import org.apache.hadoop.hdds.scm.ScmConfigKeys;
@@ -33,11 +36,16 @@
 import org.apache.hadoop.ozone.MiniOzoneHAClusterImpl;
 import org.apache.hadoop.ozone.admin.OzoneAdmin;
 import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.ozone.test.GenericTestUtils;
 import org.apache.ratis.protocol.RaftPeer;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Named;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import picocli.CommandLine.ExitCode;
 
 /**
  * Test transferLeadership with SCM HA setup.
@@ -100,29 +108,55 @@ public void testOmTransfer() throws Exception {
     assertOMResetPriorities();
   }
 
-  @Test
-  public void testScmTransfer() throws Exception {
-    StorageContainerManager oldLeader = getScmLeader(cluster);
-    List<StorageContainerManager> scmList = new ArrayList<>(cluster.
-        getStorageContainerManagersList());
-    assertThat(scmList).contains(oldLeader);
-    scmList.remove(oldLeader);
-    StorageContainerManager newLeader = scmList.get(0);
+  static Stream<Named<Function<StorageContainerManager, String>>> 
scmTargetIdExtractors() {
+    return Stream.of(
+        Named.of("scmId", StorageContainerManager::getScmId),
+        Named.of("nodeId", StorageContainerManager::getSCMNodeId));
+  }
 
-    String[] args1 = {"scm", "transfer", "-n", newLeader.getScmId()};
-    ozoneAdmin.execute(args1);
+  @ParameterizedTest
+  @MethodSource("scmTargetIdExtractors")
+  public void testScmTransfer(Function<StorageContainerManager, String> 
targetId) throws Exception {
+    StorageContainerManager newLeader = pickScmFollower();
+
+    String[] args = {"scm", "transfer", "-n", targetId.apply(newLeader)};
+    ozoneAdmin.execute(args);
     cluster.waitForClusterToBeReady();
     assertEquals(newLeader, getScmLeader(cluster));
     assertSCMResetPriorities();
+  }
 
-    oldLeader = getScmLeader(cluster);
-    String[] args3 = {"scm", "transfer", "-r"};
-    ozoneAdmin.execute(args3);
+  @Test
+  public void testScmTransferToRandomFollower() throws Exception {
+    StorageContainerManager oldLeader = getScmLeader(cluster);
+
+    String[] args = {"scm", "transfer", "-r"};
+    ozoneAdmin.execute(args);
     cluster.waitForClusterToBeReady();
     assertNotSame(oldLeader, getScmLeader(cluster));
     assertSCMResetPriorities();
   }
 
+  @Test
+  public void testScmTransferToUnknownIdFails() throws Exception {
+    StorageContainerManager leader = getScmLeader(cluster);
+
+    String[] args = {"scm", "transfer", "-n", "no-such-scm"};
+    try (GenericTestUtils.SystemErrCapturer capture = new 
GenericTestUtils.SystemErrCapturer()) {
+      assertNotEquals(ExitCode.OK, ozoneAdmin.execute(args));
+      assertThat(capture.getOutput()).contains("Target no-such-scm not found 
in group");
+    }
+    assertEquals(leader, getScmLeader(cluster));
+  }
+
+  private StorageContainerManager pickScmFollower() {
+    StorageContainerManager leader = getScmLeader(cluster);
+    List<StorageContainerManager> scmList = new 
ArrayList<>(cluster.getStorageContainerManagersList());
+    assertThat(scmList).contains(leader);
+    scmList.remove(leader);
+    return scmList.get(0);
+  }
+
   private void assertOMResetPriorities() {
     final Collection<RaftPeer> raftPeers = cluster.getOMLeader()
         .getOmRatisServer()


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

Reply via email to