You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by sz...@apache.org on 2023/02/14 08:04:28 UTC

[ozone] branch master updated: HDDS-7687. Support OM transfer Ratis leadership (#4265)

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

szetszwo 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 469c034423 HDDS-7687. Support OM transfer Ratis leadership (#4265)
469c034423 is described below

commit 469c034423bc079a4a9c5eb993f2c706dbe1d0a7
Author: Nibiru <ax...@qq.com>
AuthorDate: Tue Feb 14 16:04:22 2023 +0800

    HDDS-7687. Support OM transfer Ratis leadership (#4265)
---
 .../org/apache/hadoop/hdds/ratis/RatisHelper.java  |  59 ++++++++++++
 .../interface-client/src/main/proto/hdds.proto     |   7 ++
 .../main/java/org/apache/hadoop/ozone/OmUtils.java |   1 +
 .../org/apache/hadoop/ozone/audit/OMAction.java    |   1 +
 .../ozone/om/protocol/OzoneManagerProtocol.java    |   8 ++
 ...OzoneManagerProtocolClientSideTranslatorPB.java |  14 +++
 .../dist/src/main/smoketest/omha/testOMHA.robot    |  46 +++++++++
 .../ozone/shell/TestTransferLeadershipShell.java   | 105 +++++++++++++++++++++
 .../src/main/proto/OmClientProtocol.proto          |   6 ++
 .../org/apache/hadoop/ozone/om/OzoneManager.java   |  48 ++++++++++
 .../protocolPB/OzoneManagerRequestHandler.java     |  14 +++
 .../org/apache/hadoop/ozone/admin/om/OMAdmin.java  |   3 +-
 .../ozone/admin/om/TransferOmLeaderSubCommand.java |  73 ++++++++++++++
 13 files changed, 384 insertions(+), 1 deletion(-)

diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/RatisHelper.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/RatisHelper.java
index 84a882560b..b24ffddeec 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/RatisHelper.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/RatisHelper.java
@@ -52,6 +52,7 @@ import org.apache.ratis.grpc.GrpcConfigKeys;
 import org.apache.ratis.grpc.GrpcTlsConfig;
 import org.apache.ratis.netty.NettyConfigKeys;
 import org.apache.ratis.proto.RaftProtos;
+import org.apache.ratis.protocol.RaftClientReply;
 import org.apache.ratis.protocol.RaftGroup;
 import org.apache.ratis.protocol.RaftGroupId;
 import org.apache.ratis.protocol.RaftPeer;
@@ -488,4 +489,62 @@ public final class RatisHelper {
     }
     log.debug("{}: {}\n  {}", name, buf, builder);
   }
+
+
+  /**
+   * Use raft client to send admin request, transfer the leadership.
+   * 1. Set priority and send setConfiguration request
+   * 2. Trigger transferLeadership API.
+   *
+   * @param raftGroup     the Raft group
+   * @param targetPeerId  the target expected leader
+   * @throws IOException
+   */
+  public static void transferRatisLeadership(ConfigurationSource conf,
+      RaftGroup raftGroup, RaftPeerId targetPeerId)
+      throws IOException {
+    // TODO: need a common raft client related conf.
+    try (RaftClient raftClient = newRaftClient(SupportedRpcType.GRPC, null,
+        null, raftGroup, createRetryPolicy(conf), null, conf)) {
+      if (raftGroup.getPeer(targetPeerId) == null) {
+        throw new IOException("Cannot choose the target leader. The expected " +
+            "leader RaftPeerId is " + targetPeerId + " and the peers are " +
+            raftGroup.getPeers().stream().map(RaftPeer::getId)
+                .collect(Collectors.toList()) + ".");
+      }
+      LOG.info("Chosen the targetLeaderId {} to transfer leadership",
+          targetPeerId);
+
+      // Set priority
+      List<RaftPeer> peersWithNewPriorities = new ArrayList<>();
+      for (RaftPeer peer : raftGroup.getPeers()) {
+        peersWithNewPriorities.add(
+            RaftPeer.newBuilder(peer)
+                .setPriority(peer.getId().equals(targetPeerId) ? 2 : 1)
+                .build()
+        );
+      }
+      RaftClientReply reply;
+      // Set new configuration
+      reply = raftClient.admin().setConfiguration(peersWithNewPriorities);
+      if (reply.isSuccess()) {
+        LOG.info("Successfully set new priority for division: {}",
+            peersWithNewPriorities);
+      } else {
+        LOG.warn("Failed to set new priority for division: {}." +
+            " Ratis reply: {}", peersWithNewPriorities, reply);
+        throw new IOException(reply.getException());
+      }
+
+      // Trigger the transferLeadership
+      reply = raftClient.admin().transferLeadership(targetPeerId, 60000);
+      if (reply.isSuccess()) {
+        LOG.info("Successfully transferred leadership to {}.", targetPeerId);
+      } else {
+        LOG.warn("Failed to transfer leadership to {}. Ratis reply: {}",
+            targetPeerId, reply);
+        throw new IOException(reply.getException());
+      }
+    }
+  }
 }
diff --git a/hadoop-hdds/interface-client/src/main/proto/hdds.proto b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
index 2ff39cab58..2a07d2dcc5 100644
--- a/hadoop-hdds/interface-client/src/main/proto/hdds.proto
+++ b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
@@ -452,3 +452,10 @@ message ContainerBalancerConfigurationProto {
     required bool shouldRun = 18;
     optional int32 nextIterationIndex = 19;
 }
+
+message TransferLeadershipRequestProto {
+    required string newLeaderId = 1;
+}
+
+message TransferLeadershipResponseProto {
+}
\ No newline at end of file
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
index 1419a4cdab..1eea3abc3f 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
@@ -280,6 +280,7 @@ public final class OmUtils {
       // operation SetRangerServiceVersion.
     case GetKeyInfo:
     case SnapshotDiff:
+    case TransferLeadership:
       return true;
     case CreateVolume:
     case SetVolumeProperty:
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
index a33e5d073f..30a587cf3e 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
@@ -61,6 +61,7 @@ public enum OMAction implements AuditAction {
   RENEW_DELEGATION_TOKEN,
   CANCEL_DELEGATION_TOKEN,
   GET_SERVICE_LIST,
+  TRANSFER_LEADERSHIP,
 
   //ACL Actions
   ADD_ACL,
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
index 171b8d04d3..5c1fa78a4d 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
@@ -399,6 +399,14 @@ public interface OzoneManagerProtocol
 
   ServiceInfoEx getServiceInfo() throws IOException;
 
+  /**
+   * Transfer the raft leadership.
+   *
+   * @param newLeaderId  the newLeaderId of the target expected leader
+   * @throws IOException
+   */
+  void transferLeadership(String newLeaderId) throws IOException;
+
   /**
    * Triggers Ranger background sync task immediately.
    *
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
index fdad0bda97..538583187e 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
@@ -28,6 +28,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.hdds.annotation.InterfaceAudience;
 import org.apache.hadoop.hdds.client.ECReplicationConfig;
 import org.apache.hadoop.hdds.client.ReplicationConfig;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.TransferLeadershipRequestProto;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos
     .UpgradeFinalizationStatus;
 import org.apache.hadoop.hdds.scm.container.common.helpers.ExcludeList;
@@ -1574,6 +1575,19 @@ public final class OzoneManagerProtocolClientSideTranslatorPB
         resp.getCaCertificate(), resp.getCaCertsList());
   }
 
+  @Override
+  public void transferLeadership(String newLeaderId)
+      throws IOException {
+    TransferLeadershipRequestProto.Builder builder =
+        TransferLeadershipRequestProto.newBuilder();
+    builder.setNewLeaderId(newLeaderId);
+    OMRequest omRequest = createOMRequest(Type.TransferLeadership)
+        .setTransferOmLeadershipRequest(builder.build())
+        .build();
+    handleError(submitRequest(omRequest));
+  }
+
+
   @Override
   public boolean triggerRangerBGSync(boolean noWait) throws IOException {
     RangerBGSyncRequest req = RangerBGSyncRequest.newBuilder()
diff --git a/hadoop-ozone/dist/src/main/smoketest/omha/testOMHA.robot b/hadoop-ozone/dist/src/main/smoketest/omha/testOMHA.robot
index c3d3c7c981..9793a31149 100644
--- a/hadoop-ozone/dist/src/main/smoketest/omha/testOMHA.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/omha/testOMHA.robot
@@ -102,6 +102,18 @@ Get OM Leader Node
                             LOG                     Leader OM: ${leaderOM}
     [return]                ${leaderOM}
 
+Get One OM Follower Node
+    ${result} =             Execute                 ozone admin om roles --service-id=omservice
+                            LOG                     ${result}
+                            Should Contain          ${result}               LEADER              1
+                            Should Contain          ${result}               FOLLOWER            2
+    ${omLines} =            Get Lines Containing String                     ${result}           FOLLOWER
+    ${omLine} =             Get Line                ${omLines}              0
+    ${split1}               ${split2}               Split String            ${omLine}           :
+    ${followerOM} =         Strip String            ${split1}
+                            LOG                     Follower OM: ${followerOM}
+    [return]                ${followerOM}
+
 Get Ratis Logs
     [arguments]             ${OM_HOST}
                             Set Global Variable     ${HOST}                 ${OM_HOST}
@@ -182,4 +194,38 @@ Restart OM and Verify Ratis Logs
     # Verify that the logs match with the Leader OMs logs
     List Should Contain Sub List    ${logsAfter}        ${logsLeader}
 
+Transfer Leadership for OM
+    # Check OM write operation before failover
+    Create volume and bucket
+    Write Test File
+
+    # Find Leader OM and one Follower OM
+    ${leaderOM} =           Get OM Leader Node
+                            LOG                     Leader OM: ${leaderOM}
+    ${followerOM} =         Get One OM Follower Node
+                            LOG                     Follower OM: ${followerOM}
+    # Transfer leadership to the Follower OM
+    ${result} =             Execute                 ozone admin om transfer --service-id=omservice -n ${followerOM}
+                            LOG                     ${result}
+                            Should Contain          ${result}               Transfer leadership successfully
+
+    ${newLeaderOM} =        Get OM Leader Node
+                            Should be Equal         ${followerOM}           ${newLeaderOM}
+    Write Test File
+
+Transfer Leadership for OM randomly
+    # Check OM write operation before failover
+    Create volume and bucket
+    Write Test File
 
+    # Find Leader OM and one Follower OM
+    ${leaderOM} =           Get OM Leader Node
+                            LOG                     Leader OM: ${leaderOM}
+    # Transfer leadership to the Follower OM
+    ${result} =             Execute                 ozone admin om transfer -r
+                            LOG                     ${result}
+                            Should Contain          ${result}               Transfer leadership successfully
+
+    ${newLeaderOM} =        Get OM Leader Node
+                            Should Not be Equal     ${leaderOM}             ${newLeaderOM}
+    Write Test File
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
new file mode 100644
index 0000000000..06241753b1
--- /dev/null
+++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestTransferLeadershipShell.java
@@ -0,0 +1,105 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership.  The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.hadoop.ozone.shell;
+
+
+import org.apache.hadoop.hdds.cli.OzoneAdmin;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.scm.ScmConfigKeys;
+import org.apache.hadoop.ozone.MiniOzoneCluster;
+import org.apache.hadoop.ozone.MiniOzoneHAClusterImpl;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Test transferLeadership with SCM HA setup.
+ */
+public class TestTransferLeadershipShell {
+  private MiniOzoneHAClusterImpl cluster = null;
+  private OzoneConfiguration conf;
+  private String clusterId;
+  private String scmId;
+  private String omServiceId;
+  private String scmServiceId;
+  private int numOfOMs = 3;
+  private int numOfSCMs = 3;
+
+  private static final long SNAPSHOT_THRESHOLD = 5;
+
+  /**
+   * Create a MiniOzoneCluster for testing.
+   *
+   * @throws IOException Exception
+   */
+  @BeforeEach
+  public void init() throws Exception {
+    conf = new OzoneConfiguration();
+    clusterId = UUID.randomUUID().toString();
+    scmId = UUID.randomUUID().toString();
+    omServiceId = "om-service-test1";
+    scmServiceId = "scm-service-test1";
+    conf.setLong(ScmConfigKeys.OZONE_SCM_HA_RATIS_SNAPSHOT_THRESHOLD,
+        SNAPSHOT_THRESHOLD);
+
+    cluster = (MiniOzoneHAClusterImpl) MiniOzoneCluster.newHABuilder(conf)
+        .setClusterId(clusterId).setScmId(scmId).setOMServiceId(omServiceId)
+        .setSCMServiceId(scmServiceId).setNumOfOzoneManagers(numOfOMs)
+        .setNumOfStorageContainerManagers(numOfSCMs)
+        .setNumOfActiveSCMs(numOfSCMs).setNumOfActiveOMs(numOfOMs)
+        .build();
+    cluster.waitForClusterToBeReady();
+  }
+
+  /**
+   * Shutdown MiniDFSCluster.
+   */
+  @AfterEach
+  public void shutdown() {
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+  }
+
+  @Test
+  public void testOmTransfer() throws Exception {
+    OzoneManager oldLeader = cluster.getOMLeader();
+    List<OzoneManager> omList = new ArrayList<>(cluster.getOzoneManagersList());
+    Assertions.assertTrue(omList.contains(oldLeader));
+    omList.remove(oldLeader);
+    OzoneManager newLeader = omList.get(0);
+    cluster.waitForClusterToBeReady();
+    OzoneAdmin ozoneAdmin = new OzoneAdmin(conf);
+    String[] args1 = {"om", "transfer", "-n", newLeader.getOMNodeId()};
+    ozoneAdmin.execute(args1);
+    Thread.sleep(3000);
+    Assertions.assertEquals(newLeader, cluster.getOMLeader());
+
+    oldLeader = cluster.getOMLeader();
+    String[] args3 = {"om", "transfer", "-r"};
+    ozoneAdmin.execute(args3);
+    Thread.sleep(3000);
+    Assertions.assertNotSame(oldLeader, cluster.getOMLeader());
+  }
+}
diff --git a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index c27a14a5d9..01b0eab792 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -130,6 +130,8 @@ enum Type {
   ListSnapshot = 113;
   SnapshotDiff = 114;
   DeleteSnapshot = 115;
+
+  TransferLeadership = 116;
 }
 
 message OMRequest {
@@ -243,6 +245,8 @@ message OMRequest {
   optional SnapshotDiffRequest              snapshotDiffRequest            = 114;
   optional DeleteSnapshotRequest            DeleteSnapshotRequest          = 115;
 
+  optional hdds.TransferLeadershipRequestProto      TransferOmLeadershipRequest    = 116;
+
 }
 
 message OMResponse {
@@ -348,6 +352,8 @@ message OMResponse {
   optional ListSnapshotResponse              ListSnapshotResponse          = 113;
   optional SnapshotDiffResponse              snapshotDiffResponse          = 114;
   optional DeleteSnapshotResponse            DeleteSnapshotResponse        = 115;
+
+  optional hdds.TransferLeadershipResponseProto   TransferOmLeadershipResponse  = 116;
 }
 
 enum Status {
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index 9326475e12..0a25300d50 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -74,6 +74,7 @@ import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
 import org.apache.hadoop.hdds.protocol.proto.ReconfigureProtocolProtos.ReconfigureProtocolService;
 import org.apache.hadoop.hdds.protocolPB.ReconfigureProtocolPB;
 import org.apache.hadoop.hdds.protocolPB.ReconfigureProtocolServerSideTranslatorPB;
+import org.apache.hadoop.hdds.ratis.RatisHelper;
 import org.apache.hadoop.hdds.scm.ScmInfo;
 import org.apache.hadoop.hdds.scm.client.HddsClientUtils;
 import org.apache.hadoop.hdds.server.OzoneAdmins;
@@ -289,6 +290,7 @@ import org.apache.ratis.proto.RaftProtos.RaftPeerRole;
 import org.apache.ratis.protocol.RaftGroupId;
 import org.apache.ratis.protocol.RaftPeer;
 import org.apache.ratis.protocol.RaftPeerId;
+import org.apache.ratis.server.RaftServer;
 import org.apache.ratis.server.protocol.TermIndex;
 import org.apache.ratis.util.ExitUtils;
 import org.apache.ratis.util.FileUtils;
@@ -3023,6 +3025,52 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
     return new ServiceInfoEx(getServiceList(), caCertPem, caCertPemList);
   }
 
+  @Override
+  public void transferLeadership(String newLeaderId)
+      throws IOException {
+    final UserGroupInformation ugi = getRemoteUser();
+    if (!isAdmin(ugi)) {
+      throw new OMException(
+          "Only Ozone admins are allowed to transfer raft leadership.",
+          PERMISSION_DENIED);
+    }
+    if (!isRatisEnabled) {
+      throw new IOException("OM HA not enabled.");
+    }
+    boolean auditSuccess = true;
+    Map<String, String> auditMap = new LinkedHashMap<>();
+    auditMap.put("newLeaderId", newLeaderId);
+    try {
+      RaftGroupId groupID = omRatisServer.getRaftGroup().getGroupId();
+      RaftServer.Division division = omRatisServer.getServer()
+          .getDivision(groupID);
+      RaftPeerId targetPeerId;
+      if (newLeaderId.isEmpty()) {
+        RaftPeer curLeader = omRatisServer.getLeader();
+        targetPeerId = division.getGroup()
+            .getPeers().stream().filter(a -> !a.equals(curLeader)).findFirst()
+            .map(RaftPeer::getId).orElseThrow(() -> new IOException("Cannot" +
+                " find a new leader to transfer leadership."));
+      } else {
+        targetPeerId = RaftPeerId.valueOf(newLeaderId);
+      }
+      RatisHelper.transferRatisLeadership(configuration, division.getGroup(),
+          targetPeerId);
+    } catch (IOException ex) {
+      auditSuccess = false;
+      AUDIT.logReadFailure(
+          buildAuditMessageForFailure(OMAction.TRANSFER_LEADERSHIP,
+              auditMap, ex));
+      throw ex;
+    } finally {
+      if (auditSuccess) {
+        AUDIT.logReadSuccess(
+            buildAuditMessageForSuccess(OMAction.TRANSFER_LEADERSHIP,
+                auditMap));
+      }
+    }
+  }
+
   @Override
   public boolean triggerRangerBGSync(boolean noWait) throws IOException {
 
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
index 6ab84a5f02..1032de2542 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
@@ -30,6 +30,8 @@ import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.hadoop.hdds.client.ECReplicationConfig;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.TransferLeadershipRequestProto;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.TransferLeadershipResponseProto;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos.UpgradeFinalizationStatus;
 import org.apache.hadoop.hdds.client.ReplicationConfig;
 import org.apache.hadoop.hdds.utils.db.SequenceNumberNotFoundException;
@@ -303,6 +305,11 @@ public class OzoneManagerRequestHandler implements RequestHandler {
         EchoRPCResponse echoRPCResponse =
             echoRPC(request.getEchoRPCRequest());
         responseBuilder.setEchoRPCResponse(echoRPCResponse);
+        break;
+      case TransferLeadership:
+        responseBuilder.setTransferOmLeadershipResponse(transferLeadership(
+            request.getTransferOmLeadershipRequest()));
+        break;
       default:
         responseBuilder.setSuccess(false);
         responseBuilder.setMessage("Unrecognized Command Type: " + cmdType);
@@ -1250,4 +1257,11 @@ public class OzoneManagerRequestHandler implements RequestHandler {
     return OzoneManagerProtocolProtos.ListSnapshotResponse.newBuilder()
         .addAllSnapshotInfo(snapshotInfoList).build();
   }
+
+  private TransferLeadershipResponseProto transferLeadership(
+      TransferLeadershipRequestProto req) throws IOException {
+    String newLeaderId = req.getNewLeaderId();
+    impl.transferLeadership(newLeaderId);
+    return TransferLeadershipResponseProto.getDefaultInstance();
+  }
 }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/OMAdmin.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/OMAdmin.java
index b61438077f..9bb447669d 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/OMAdmin.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/OMAdmin.java
@@ -58,7 +58,8 @@ import java.util.Collection;
         CancelPrepareSubCommand.class,
         FinalizationStatusSubCommand.class,
         DecommissionOMSubcommand.class,
-        UpdateRangerSubcommand.class
+        UpdateRangerSubcommand.class,
+        TransferOmLeaderSubCommand.class
     })
 @MetaInfServices(SubcommandWithParent.class)
 public class OMAdmin extends GenericCli implements SubcommandWithParent {
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/TransferOmLeaderSubCommand.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/TransferOmLeaderSubCommand.java
new file mode 100644
index 0000000000..32e432cd04
--- /dev/null
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/TransferOmLeaderSubCommand.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership.  The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.hadoop.ozone.admin.om;
+
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
+import picocli.CommandLine;
+import java.util.concurrent.Callable;
+
+
+/**
+ * Handler of ozone admin om transfer command.
+ */
+@CommandLine.Command(
+    name = "transfer",
+    description = "Manually transfer the raft leadership to the target node.",
+    mixinStandardHelpOptions = true,
+    versionProvider = HddsVersionProvider.class
+)
+public class TransferOmLeaderSubCommand implements Callable<Void> {
+
+  @CommandLine.ParentCommand
+  private OMAdmin parent;
+
+  @CommandLine.Option(
+      names = {"-id", "--service-id"},
+      description = "Ozone Manager Service ID."
+  )
+  private String omServiceId;
+
+  @CommandLine.ArgGroup(multiplicity = "1")
+  private TransferOption configGroup;
+
+  static class TransferOption {
+    @CommandLine.Option(
+        names = {"-n", "--newLeaderId"},
+        description = "The new leader id of OM to transfer leadership. E.g OM1."
+    )
+    private String omNodeId;
+
+    @CommandLine.Option(names = {"-r", "--random"},
+        description = "Randomly choose a follower to transfer leadership.")
+    private boolean isRandom;
+  }
+
+  @Override
+  public Void call() throws Exception {
+    OzoneManagerProtocol client =
+        parent.createOmClient(omServiceId, null, true);
+    if (configGroup.isRandom) {
+      configGroup.omNodeId = "";
+    }
+    client.transferLeadership(configGroup.omNodeId);
+    System.out.println("Transfer leadership successfully to " +
+        (configGroup.isRandom ? "random node" : configGroup.omNodeId) + ".");
+    return null;
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org