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

sammichen 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 50e57f9af5 HDDS-7137. Add CLI for Getting the failed deleted block txn 
(#3691)
50e57f9af5 is described below

commit 50e57f9af5a8835815fd54fb0d39279f40254889
Author: NibiruXu <[email protected]>
AuthorDate: Mon Mar 6 16:33:48 2023 +0800

    HDDS-7137. Add CLI for Getting the failed deleted block txn (#3691)
---
 .../apache/hadoop/hdds/scm/client/ScmClient.java   |  16 +-
 .../protocol/StorageContainerLocationProtocol.java |  16 +-
 .../org/apache/hadoop/ozone/audit/SCMAction.java   |   3 +-
 .../DeletedBlocksTransactionInfoWrapper.java       | 110 +++++++++
 ...inerLocationProtocolClientSideTranslatorPB.java |  18 ++
 .../src/main/proto/ScmAdminProtocol.proto          |  13 +
 .../interface-client/src/main/proto/hdds.proto     |   7 +
 .../hadoop/hdds/scm/block/DeletedBlockLog.java     |  12 +-
 .../hadoop/hdds/scm/block/DeletedBlockLogImpl.java |  25 +-
 ...inerLocationProtocolServerSideTranslatorPB.java |  19 ++
 .../hdds/scm/server/SCMClientProtocolServer.java   |  22 ++
 .../hdds/scm/cli/ContainerOperationClient.java     |   8 +
 .../hadoop/ozone/TestStorageContainerManager.java  |   2 +-
 .../ozone/shell/TestDeletedBlocksTxnShell.java     | 270 +++++++++++++++++++++
 .../TestResetDeletedBlockRetryCountShell.java      | 169 -------------
 ...ScmAdmin.java => DeletedBlocksTxnCommands.java} |  39 +--
 .../scm/GetFailedDeletedBlocksTxnSubcommand.java   |  91 +++++++
 .../scm/ResetDeletedBlockRetryCountSubcommand.java |  57 ++++-
 .../apache/hadoop/ozone/admin/scm/ScmAdmin.java    |   4 +-
 19 files changed, 675 insertions(+), 226 deletions(-)

diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
index 713d7204c5..916bd7fdfb 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/client/ScmClient.java
@@ -20,6 +20,7 @@ package org.apache.hadoop.hdds.scm.client;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.hadoop.hdds.annotation.InterfaceStability;
 import org.apache.hadoop.hdds.client.ReplicationConfig;
+import 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionInfo;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.StartContainerBalancerResponseProto;
 import org.apache.hadoop.hdds.scm.DatanodeAdminError;
 import org.apache.hadoop.hdds.scm.container.ContainerReplicaInfo;
@@ -369,7 +370,20 @@ public interface ScmClient extends Closeable {
   void transferLeadership(String newLeaderId) throws IOException;
 
   /**
-   * Reset the expired deleted block retry count.
+   * Return the failed transactions of the Deleted blocks. A transaction is
+   * considered to be failed if it has been sent more than MAX_RETRY limit
+   * and its count is reset to -1.
+   *
+   * @param count Maximum num of returned transactions, if < 0. return all.
+   * @param startTxId The least transaction id to start with.
+   * @return a list of failed deleted block transactions.
+   * @throws IOException
+   */
+  List<DeletedBlocksTransactionInfo> getFailedDeletedBlockTxn(int count,
+      long startTxId) throws IOException;
+
+  /**
+   * Reset the failed deleted block retry count.
    * @param txIDs transactionId list to be reset
    * @throws IOException
    */
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
index bd03bc84e8..7690b2eefb 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocol.java
@@ -20,6 +20,7 @@ package org.apache.hadoop.hdds.scm.protocol;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.hadoop.hdds.client.ReplicationConfig;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionInfo;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.StartContainerBalancerResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.Type;
 import org.apache.hadoop.hdds.scm.DatanodeAdminError;
@@ -317,7 +318,20 @@ public interface StorageContainerLocationProtocol extends 
Closeable {
   void transferLeadership(String newLeaderId) throws IOException;
 
   /**
-   * Reset the expired deleted block retry count.
+   * Return the failed transactions of the Deleted blocks. A transaction is
+   * considered to be failed if it has been sent more than MAX_RETRY limit
+   * and its count is reset to -1.
+   *
+   * @param count Maximum num of returned transactions, if < 0. return all.
+   * @param startTxId The least transaction id to start with.
+   * @return a list of failed deleted block transactions.
+   * @throws IOException
+   */
+  List<DeletedBlocksTransactionInfo> getFailedDeletedBlockTxn(int count,
+      long startTxId) throws IOException;
+
+  /**
+   * Reset the failed deleted block retry count.
    *
    * @param txIDs transactionId list to be reset
    * @return num of successful reset
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/audit/SCMAction.java 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/audit/SCMAction.java
index 16e88df281..4e1fe234ff 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/audit/SCMAction.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/audit/SCMAction.java
@@ -51,7 +51,8 @@ public enum SCMAction implements AuditAction {
   ADD_SCM,
   GET_REPLICATION_MANAGER_REPORT,
   RESET_DELETED_BLOCK_RETRY_COUNT,
-  TRANSFER_LEADERSHIP;
+  TRANSFER_LEADERSHIP,
+  GET_FAILED_DELETED_BLOCKS_TRANSACTION;
 
   @Override
   public String getAction() {
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/container/common/helpers/DeletedBlocksTransactionInfoWrapper.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/container/common/helpers/DeletedBlocksTransactionInfoWrapper.java
new file mode 100644
index 0000000000..64ced8dce4
--- /dev/null
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/container/common/helpers/DeletedBlocksTransactionInfoWrapper.java
@@ -0,0 +1,110 @@
+/*
+ * 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
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.hdds.scm.container.common.helpers;
+
+import 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionInfo;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction;
+import java.util.List;
+
+/**
+ * The wrapper for {@link DeletedBlocksTransactionInfo}.
+ */
+public class DeletedBlocksTransactionInfoWrapper {
+
+  private final long txID;
+  private final long containerID;
+  private final List<Long> localIdList;
+  private final int count;
+
+  public DeletedBlocksTransactionInfoWrapper(long txID, long containerID,
+      List<Long> localIdList, int count) {
+    this.txID = txID;
+    this.containerID = containerID;
+    this.localIdList = localIdList;
+    this.count = count;
+  }
+
+  public static DeletedBlocksTransactionInfoWrapper fromProtobuf(
+      DeletedBlocksTransactionInfo txn) {
+    if (txn.hasTxID() && txn.hasContainerID() && txn.hasCount()) {
+      return new DeletedBlocksTransactionInfoWrapper(
+          txn.getTxID(),
+          txn.getContainerID(),
+          txn.getLocalIDList(),
+          txn.getCount());
+    }
+    return null;
+  }
+
+  public static DeletedBlocksTransactionInfo toProtobuf(
+      DeletedBlocksTransactionInfoWrapper wrapper) {
+    return DeletedBlocksTransactionInfo.newBuilder()
+        .setTxID(wrapper.txID)
+        .setContainerID(wrapper.containerID)
+        .addAllLocalID(wrapper.localIdList)
+        .setCount(wrapper.count)
+        .build();
+  }
+
+  public static DeletedBlocksTransactionInfo fromTxn(
+      DeletedBlocksTransaction txn) {
+    return DeletedBlocksTransactionInfo.newBuilder()
+        .setTxID(txn.getTxID())
+        .setContainerID(txn.getContainerID())
+        .addAllLocalID(txn.getLocalIDList())
+        .setCount(txn.getCount())
+        .build();
+  }
+
+  public static DeletedBlocksTransaction toTxn(
+      DeletedBlocksTransactionInfo info) {
+    return DeletedBlocksTransaction.newBuilder()
+        .setTxID(info.getTxID())
+        .setContainerID(info.getContainerID())
+        .addAllLocalID(info.getLocalIDList())
+        .setCount(info.getCount())
+        .build();
+  }
+
+
+  public long getTxID() {
+    return txID;
+  }
+
+  public long getContainerID() {
+    return containerID;
+  }
+
+  public List<Long> getLocalIdList() {
+    return localIdList;
+  }
+
+  public int getCount() {
+    return count;
+  }
+
+  @Override
+  public String toString() {
+    return "DeletedBlocksTransactionInfoWrapper{" +
+        "txID=" + txID +
+        ", containerID=" + containerID +
+        ", localIdList=" + localIdList +
+        ", count=" + count +
+        '}';
+  }
+}
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
index 0f7d1c0bb5..c664a42ae6 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/StorageContainerLocationProtocolClientSideTranslatorPB.java
@@ -25,12 +25,15 @@ import org.apache.hadoop.hdds.client.ECReplicationConfig;
 import org.apache.hadoop.hdds.client.ReplicatedReplicationConfig;
 import org.apache.hadoop.hdds.client.ReplicationConfig;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionInfo;
 import 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.GetScmInfoResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.TransferLeadershipRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.UpgradeFinalizationStatus;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.FinalizeScmUpgradeRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.FinalizeScmUpgradeResponseProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetFailedDeletedBlocksTxnRequestProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetFailedDeletedBlocksTxnResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.QueryUpgradeFinalizationProgressRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.QueryUpgradeFinalizationProgressResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.SafeModeRuleStatusProto;
@@ -713,6 +716,21 @@ public final class 
StorageContainerLocationProtocolClientSideTranslatorPB
         builder -> 
builder.setTransferScmLeadershipRequest(reqBuilder.build()));
   }
 
+  @Override
+  public List<DeletedBlocksTransactionInfo> getFailedDeletedBlockTxn(int count,
+      long startTxId) throws IOException {
+    GetFailedDeletedBlocksTxnRequestProto request =
+        GetFailedDeletedBlocksTxnRequestProto.newBuilder()
+            .setCount(count)
+            .setStartTxId(startTxId)
+            .build();
+    GetFailedDeletedBlocksTxnResponseProto resp = submitRequest(
+        Type.GetFailedDeletedBlocksTransaction,
+        builder -> builder.setGetFailedDeletedBlocksTxnRequest(request)).
+        getGetFailedDeletedBlocksTxnResponse();
+    return resp.getDeletedBlocksTransactionsList();
+  }
+
   @Override
   public int resetDeletedBlockRetryCount(List<Long> txIDs)
       throws IOException {
diff --git a/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto 
b/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
index 2aac8b2f51..d5a3c6f65a 100644
--- a/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
+++ b/hadoop-hdds/interface-admin/src/main/proto/ScmAdminProtocol.proto
@@ -79,6 +79,7 @@ message ScmContainerLocationRequest {
   optional ReplicationManagerReportRequestProto 
replicationManagerReportRequest = 40;
   optional ResetDeletedBlockRetryCountRequestProto 
resetDeletedBlockRetryCountRequest = 41;
   optional TransferLeadershipRequestProto  transferScmLeadershipRequest = 42;
+  optional GetFailedDeletedBlocksTxnRequestProto 
getFailedDeletedBlocksTxnRequest = 43;
 }
 
 message ScmContainerLocationResponse {
@@ -129,6 +130,7 @@ message ScmContainerLocationResponse {
   optional ReplicationManagerReportResponseProto 
getReplicationManagerReportResponse = 40;
   optional ResetDeletedBlockRetryCountResponseProto 
resetDeletedBlockRetryCountResponse = 41;
   optional TransferLeadershipResponseProto  transferScmLeadershipResponse = 42;
+  optional GetFailedDeletedBlocksTxnResponseProto 
getFailedDeletedBlocksTxnResponse = 43;
 
   enum Status {
     OK = 1;
@@ -178,6 +180,7 @@ enum Type {
   ResetDeletedBlockRetryCount = 36;
   GetClosedContainerCount = 37;
   TransferLeadership = 38;
+  GetFailedDeletedBlocksTransaction = 39;
 }
 
 /**
@@ -488,6 +491,16 @@ message ReplicationManagerReportResponseProto {
   required ReplicationManagerReportProto report = 1;
 }
 
+message GetFailedDeletedBlocksTxnRequestProto {
+  optional string traceID = 1;
+  required int32 count = 2;
+  optional int64 startTxId = 3;
+}
+
+message GetFailedDeletedBlocksTxnResponseProto {
+  repeated DeletedBlocksTransactionInfo deletedBlocksTransactions = 1;
+}
+
 message ResetDeletedBlockRetryCountRequestProto {
   optional string traceID = 1;
   repeated int64 transactionId = 2;
diff --git a/hadoop-hdds/interface-client/src/main/proto/hdds.proto 
b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
index 2a07d2dcc5..16ea4887aa 100644
--- a/hadoop-hdds/interface-client/src/main/proto/hdds.proto
+++ b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
@@ -458,4 +458,11 @@ message TransferLeadershipRequestProto {
 }
 
 message TransferLeadershipResponseProto {
+}
+
+message DeletedBlocksTransactionInfo {
+    optional int64 txID = 1;
+    optional int64 containerID = 2;
+    repeated int64 localID = 3;
+    optional int32 count = 4;
 }
\ No newline at end of file
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLog.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLog.java
index ddd7085e29..ea2013917d 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLog.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLog.java
@@ -51,15 +51,17 @@ public interface DeletedBlockLog extends Closeable {
       throws IOException, TimeoutException;
 
   /**
-   * Return all failed transactions in the log. A transaction is considered
-   * to be failed if it has been sent more than MAX_RETRY limit and its
-   * count is reset to -1.
+   * Return the failed transactions in the log. A transaction is
+   * considered to be failed if it has been sent more than MAX_RETRY limit
+   * and its count is reset to -1.
    *
+   * @param count Maximum num of returned transactions, if < 0. return all.
+   * @param startTxId The least transaction id to start with.
    * @return a list of failed deleted block transactions.
    * @throws IOException
    */
-  List<DeletedBlocksTransaction> getFailedTransactions()
-      throws IOException;
+  List<DeletedBlocksTransaction> getFailedTransactions(int count,
+      long startTxId) throws IOException;
 
   /**
    * Increments count for given list of transactions by 1.
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogImpl.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogImpl.java
index 6903d5ed3f..589a9f3f76 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogImpl.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogImpl.java
@@ -92,6 +92,8 @@ public class DeletedBlockLogImpl
   private final SequenceIdGenerator sequenceIdGen;
   private final ScmBlockDeletingServiceMetrics metrics;
 
+  private static final int LIST_ALL_FAILED_TRANSACTIONS = -1;
+
   @SuppressWarnings("parameternumber")
   public DeletedBlockLogImpl(ConfigurationSource conf,
       ContainerManager containerManager,
@@ -126,18 +128,27 @@ public class DeletedBlockLogImpl
   }
 
   @Override
-  public List<DeletedBlocksTransaction> getFailedTransactions()
-      throws IOException {
+  public List<DeletedBlocksTransaction> getFailedTransactions(int count,
+      long startTxId) throws IOException {
     lock.lock();
     try {
       final List<DeletedBlocksTransaction> failedTXs = Lists.newArrayList();
       try (TableIterator<Long,
           ? extends Table.KeyValue<Long, DeletedBlocksTransaction>> iter =
                deletedBlockLogStateManager.getReadOnlyIterator()) {
-        while (iter.hasNext()) {
-          DeletedBlocksTransaction delTX = iter.next().getValue();
-          if (delTX.getCount() == -1) {
-            failedTXs.add(delTX);
+        if (count == LIST_ALL_FAILED_TRANSACTIONS) {
+          while (iter.hasNext()) {
+            DeletedBlocksTransaction delTX = iter.next().getValue();
+            if (delTX.getCount() == -1) {
+              failedTXs.add(delTX);
+            }
+          }
+        } else {
+          while (iter.hasNext() && failedTXs.size() < count) {
+            DeletedBlocksTransaction delTX = iter.next().getValue();
+            if (delTX.getCount() == -1 && delTX.getTxID() >= startTxId) {
+              failedTXs.add(delTX);
+            }
           }
         }
       }
@@ -191,7 +202,7 @@ public class DeletedBlockLogImpl
     lock.lock();
     try {
       if (txIDs == null || txIDs.isEmpty()) {
-        txIDs = getFailedTransactions().stream()
+        txIDs = getFailedTransactions(LIST_ALL_FAILED_TRANSACTIONS, 0).stream()
             .map(DeletedBlocksTransaction::getTxID)
             .collect(Collectors.toList());
       }
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocolServerSideTranslatorPB.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocolServerSideTranslatorPB.java
index e550a9bd5d..2dc3c5b04f 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocolServerSideTranslatorPB.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/protocol/StorageContainerLocationProtocolServerSideTranslatorPB.java
@@ -60,6 +60,8 @@ import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolPro
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetExistContainerWithPipelinesInBatchRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetExistContainerWithPipelinesInBatchResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetContainerCountResponseProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetFailedDeletedBlocksTxnRequestProto;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetFailedDeletedBlocksTxnResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetPipelineRequestProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetPipelineResponseProto;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.GetSafeModeRuleStatusesRequestProto;
@@ -657,6 +659,14 @@ public final class 
StorageContainerLocationProtocolServerSideTranslatorPB
               request.getGetContainerReplicasRequest(),
               request.getVersion()))
           .build();
+      case GetFailedDeletedBlocksTransaction:
+        return ScmContainerLocationResponse.newBuilder()
+            .setCmdType(request.getCmdType())
+            .setStatus(Status.OK)
+            .setGetFailedDeletedBlocksTxnResponse(getFailedDeletedBlocksTxn(
+                request.getGetFailedDeletedBlocksTxnRequest()
+            ))
+            .build();
       case ResetDeletedBlockRetryCount:
         return ScmContainerLocationResponse.newBuilder()
               .setCmdType(request.getCmdType())
@@ -1176,6 +1186,15 @@ public final class 
StorageContainerLocationProtocolServerSideTranslatorPB
         .build();
   }
 
+  public GetFailedDeletedBlocksTxnResponseProto getFailedDeletedBlocksTxn(
+      GetFailedDeletedBlocksTxnRequestProto request) throws IOException {
+    long startTxId = request.hasStartTxId() ? request.getStartTxId() : 0;
+    return GetFailedDeletedBlocksTxnResponseProto.newBuilder()
+        .addAllDeletedBlocksTransactions(
+            impl.getFailedDeletedBlockTxn(request.getCount(), startTxId))
+        .build();
+  }
+
   public ResetDeletedBlockRetryCountResponseProto
       getResetDeletedBlockRetryCount(ResetDeletedBlockRetryCountRequestProto
       request) throws IOException {
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 c73b2f0c26..ccd4153698 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
@@ -37,6 +37,7 @@ import org.apache.hadoop.hdds.protocol.DatanodeDetails;
 import org.apache.hadoop.hdds.protocol.ReconfigureProtocol;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
 import 
org.apache.hadoop.hdds.protocol.proto.ReconfigureProtocolProtos.ReconfigureProtocolService;
+import 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionInfo;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.StartContainerBalancerResponseProto;
 import org.apache.hadoop.hdds.protocolPB.ReconfigureProtocolPB;
@@ -48,6 +49,7 @@ import org.apache.hadoop.hdds.scm.container.ContainerID;
 import org.apache.hadoop.hdds.scm.container.ContainerInfo;
 import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException;
 import org.apache.hadoop.hdds.scm.container.ContainerReplica;
+import 
org.apache.hadoop.hdds.scm.container.common.helpers.DeletedBlocksTransactionInfoWrapper;
 import org.apache.hadoop.hdds.scm.container.ReplicationManagerReport;
 import org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancer;
 import 
org.apache.hadoop.hdds.scm.container.balancer.ContainerBalancerConfiguration;
@@ -846,6 +848,26 @@ public class SCMClientProtocolServer implements
     }
   }
 
+  public List<DeletedBlocksTransactionInfo> getFailedDeletedBlockTxn(int count,
+      long startTxId) throws IOException {
+    List<DeletedBlocksTransactionInfo> result;
+    try {
+      result = scm.getScmBlockManager().getDeletedBlockLog()
+          .getFailedTransactions(count, startTxId).stream()
+          .map(DeletedBlocksTransactionInfoWrapper::fromTxn)
+          .collect(Collectors.toList());
+      AUDIT.logWriteSuccess(buildAuditMessageForSuccess(
+          SCMAction.GET_FAILED_DELETED_BLOCKS_TRANSACTION, null));
+      return result;
+    } catch (IOException ex) {
+      AUDIT.logReadFailure(
+          buildAuditMessageForFailure(
+              SCMAction.GET_FAILED_DELETED_BLOCKS_TRANSACTION, null, ex)
+      );
+      throw ex;
+    }
+  }
+
   @Override
   public int resetDeletedBlockRetryCount(List<Long> txIDs) throws IOException {
     Map<String, String> auditMap = Maps.newHashMap();
diff --git 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
index d72ed7bda9..22050f1940 100644
--- 
a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
+++ 
b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/ContainerOperationClient.java
@@ -26,6 +26,7 @@ import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import 
org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ContainerDataProto;
 import 
org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos.ReadContainerResponseProto;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionInfo;
 import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerLocationProtocolProtos.StartContainerBalancerResponseProto;
 import org.apache.hadoop.hdds.scm.DatanodeAdminError;
 import org.apache.hadoop.hdds.scm.ScmConfigKeys;
@@ -469,6 +470,13 @@ public class ContainerOperationClient implements ScmClient 
{
     storageContainerLocationClient.transferLeadership(newLeaderId);
   }
 
+  @Override
+  public List<DeletedBlocksTransactionInfo> getFailedDeletedBlockTxn(int count,
+      long startTxId) throws IOException {
+    return storageContainerLocationClient.getFailedDeletedBlockTxn(count,
+        startTxId);
+  }
+
   @Override
   public int resetDeletedBlockRetryCount(List<Long> txIDs) throws IOException {
     return storageContainerLocationClient.resetDeletedBlockRetryCount(txIDs);
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestStorageContainerManager.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestStorageContainerManager.java
index 89a17f7b95..33f3e9d363 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestStorageContainerManager.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestStorageContainerManager.java
@@ -357,7 +357,7 @@ public class TestStorageContainerManager {
             cluster.getStorageContainerManager().getScmHAManager()
                 .asSCMHADBTransactionBuffer().flush();
           }
-          return delLog.getFailedTransactions().size() == 0;
+          return delLog.getFailedTransactions(-1, 0).size() == 0;
         } catch (IOException e) {
           return false;
         }
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestDeletedBlocksTxnShell.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestDeletedBlocksTxnShell.java
new file mode 100644
index 0000000000..e74041ceaf
--- /dev/null
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestDeletedBlocksTxnShell.java
@@ -0,0 +1,270 @@
+/*
+ * 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.client.RatisReplicationConfig;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import 
org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.ContainerReplicaProto.State;
+import org.apache.hadoop.hdds.scm.ScmConfigKeys;
+import org.apache.hadoop.hdds.scm.block.DeletedBlockLog;
+import org.apache.hadoop.hdds.scm.cli.ContainerOperationClient;
+import org.apache.hadoop.hdds.scm.container.ContainerID;
+import org.apache.hadoop.hdds.scm.container.ContainerInfo;
+import org.apache.hadoop.hdds.scm.container.ContainerReplica;
+import org.apache.hadoop.hdds.scm.container.ContainerStateManager;
+import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
+import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
+import org.apache.hadoop.ozone.MiniOzoneCluster;
+import org.apache.hadoop.ozone.MiniOzoneHAClusterImpl;
+import org.apache.hadoop.ozone.admin.scm.GetFailedDeletedBlocksTxnSubcommand;
+import org.apache.hadoop.ozone.admin.scm.ResetDeletedBlockRetryCountSubcommand;
+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 org.junit.jupiter.api.io.TempDir;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.THREE;
+import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_BLOCK_DELETION_MAX_RETRY;
+
+/**
+ * Test for DeletedBlocksTxnSubcommand Cli.
+ */
+public class TestDeletedBlocksTxnShell {
+
+  private static final Logger LOG = LoggerFactory
+      .getLogger(TestDeletedBlocksTxnShell.class);
+
+  private final PrintStream originalOut = System.out;
+  private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+  private MiniOzoneHAClusterImpl cluster = null;
+  private OzoneConfiguration conf;
+  private String clusterId;
+  private String scmId;
+  private String scmServiceId;
+  private File txnFile;
+  private int numOfSCMs = 3;
+
+  private static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name();
+
+  @TempDir
+  private Path tempDir;
+
+  /**
+   * Create a MiniOzoneHACluster for testing.
+   *
+   * @throws IOException
+   */
+  @BeforeEach
+  public void init() throws Exception {
+    conf = new OzoneConfiguration();
+    clusterId = UUID.randomUUID().toString();
+    scmId = UUID.randomUUID().toString();
+    scmServiceId = "scm-service-test1";
+
+    conf.setBoolean(ScmConfigKeys.OZONE_SCM_HA_ENABLE_KEY, true);
+    conf.setInt(OZONE_SCM_BLOCK_DELETION_MAX_RETRY, 20);
+
+    cluster = (MiniOzoneHAClusterImpl) MiniOzoneCluster.newOMHABuilder(conf)
+        .setClusterId(clusterId)
+        .setScmId(scmId)
+        .setSCMServiceId(scmServiceId)
+        .setNumOfStorageContainerManagers(numOfSCMs)
+        .setNumOfActiveSCMs(numOfSCMs)
+        .setNumOfOzoneManagers(1)
+        .build();
+    cluster.waitForClusterToBeReady();
+
+    txnFile = tempDir.resolve("txn.txt").toFile();
+    LOG.info("txnFile path: {}", txnFile.getAbsolutePath());
+    System.setOut(new PrintStream(outContent, false, DEFAULT_ENCODING));
+  }
+
+  /**
+   * Shutdown MiniDFSCluster.
+   */
+  @AfterEach
+  public void shutdown() {
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+    System.setOut(originalOut);
+  }
+
+  //<containerID,  List<blockID>>
+  private Map<Long, List<Long>> generateData(int dataSize) throws Exception {
+    Map<Long, List<Long>> blockMap = new HashMap<>();
+    Random random = new Random(1);
+    int continerIDBase = random.nextInt(100);
+    int localIDBase = random.nextInt(1000);
+    for (int i = 0; i < dataSize; i++) {
+      long containerID = continerIDBase + i;
+      updateContainerMetadata(containerID);
+      List<Long> blocks = new ArrayList<>();
+      for (int j = 0; j < 5; j++)  {
+        long localID = localIDBase + j;
+        blocks.add(localID);
+      }
+      blockMap.put(containerID, blocks);
+    }
+    return blockMap;
+  }
+
+  private void updateContainerMetadata(long cid) throws Exception {
+    final ContainerInfo container =
+        new ContainerInfo.Builder()
+            .setContainerID(cid)
+            .setReplicationConfig(RatisReplicationConfig.getInstance(THREE))
+            .setState(HddsProtos.LifeCycleState.CLOSED)
+            .setOwner("TestDeletedBlockLog")
+            .setPipelineID(PipelineID.randomId())
+            .build();
+    final Set<ContainerReplica> replicaSet = cluster.getHddsDatanodes()
+        .subList(0, 3)
+        .stream()
+        .map(dn -> ContainerReplica.newBuilder()
+            .setContainerID(container.containerID())
+            .setContainerState(State.CLOSED)
+            .setDatanodeDetails(dn.getDatanodeDetails())
+            .build())
+        .collect(Collectors.toSet());
+    ContainerStateManager containerStateManager = getSCMLeader().
+        getContainerManager().getContainerStateManager();
+    containerStateManager.addContainer(container.getProtobuf());
+    for (ContainerReplica replica: replicaSet) {
+      containerStateManager.updateContainerReplica(
+          ContainerID.valueOf(cid), replica);
+    }
+  }
+
+  private StorageContainerManager getSCMLeader() {
+    return cluster.getStorageContainerManagersList()
+        .stream().filter(a -> a.getScmContext().isLeaderReady())
+        .collect(Collectors.toList()).get(0);
+  }
+  
+  private void flush() throws Exception {
+    // only flush leader here, avoid the follower concurrent flush and write
+    getSCMLeader().getScmHAManager().asSCMHADBTransactionBuffer().flush();
+  }
+  
+  @Test
+  public void testDeletedBlocksTxnSubcommand() throws Exception {
+    int maxRetry = conf.getInt(OZONE_SCM_BLOCK_DELETION_MAX_RETRY, 20);
+    int currentValidTxnNum;
+    // add 30 block deletion transactions
+    DeletedBlockLog deletedBlockLog = getSCMLeader().
+        getScmBlockManager().getDeletedBlockLog();
+    deletedBlockLog.addTransactions(generateData(30));
+    flush();
+    currentValidTxnNum = deletedBlockLog.getNumOfValidTransactions();
+    LOG.info("Valid num of txns: {}", currentValidTxnNum);
+    Assertions.assertEquals(30, currentValidTxnNum);
+
+    // let the first 20 txns be failed
+    List<Long> txIds = new ArrayList<>();
+    for (int i = 1; i < 21; i++) {
+      txIds.add((long) i);
+    }
+    // increment retry count than threshold, count will be set to -1
+    for (int i = 0; i < maxRetry + 1; i++) {
+      deletedBlockLog.incrementCount(txIds);
+    }
+    flush();
+    currentValidTxnNum = deletedBlockLog.getNumOfValidTransactions();
+    LOG.info("Valid num of txns: {}", currentValidTxnNum);
+    Assertions.assertEquals(10, currentValidTxnNum);
+
+    ContainerOperationClient scmClient = new ContainerOperationClient(conf);
+    CommandLine cmd;
+    // getFailedDeletedBlocksTxn cmd will print all the failed txns
+    GetFailedDeletedBlocksTxnSubcommand getCommand =
+        new GetFailedDeletedBlocksTxnSubcommand();
+    cmd = new CommandLine(getCommand);
+    cmd.parseArgs("-a");
+    getCommand.execute(scmClient);
+    int matchCount = 0;
+    Pattern p = Pattern.compile("\"txID\" : \\d+", Pattern.MULTILINE);
+    Matcher m = p.matcher(outContent.toString(DEFAULT_ENCODING));
+    while (m.find()) {
+      matchCount += 1;
+    }
+    Assertions.assertEquals(20, matchCount);
+
+    // print the first 10 failed txns info into file
+    cmd.parseArgs("-o", txnFile.getAbsolutePath(), "-c", "10");
+    getCommand.execute(scmClient);
+    Assertions.assertTrue(txnFile.exists());
+
+    ResetDeletedBlockRetryCountSubcommand resetCommand =
+        new ResetDeletedBlockRetryCountSubcommand();
+    cmd = new CommandLine(resetCommand);
+
+    // reset the txns in file
+    cmd.parseArgs("-i", txnFile.getAbsolutePath());
+    resetCommand.execute(scmClient);
+    flush();
+    currentValidTxnNum = deletedBlockLog.getNumOfValidTransactions();
+    LOG.info("Valid num of txns: {}", currentValidTxnNum);
+    Assertions.assertEquals(20, currentValidTxnNum);
+
+    // reset the given txIds list
+    cmd.parseArgs("-l", "11,12,13,14,15");
+    resetCommand.execute(scmClient);
+    flush();
+    currentValidTxnNum = deletedBlockLog.getNumOfValidTransactions();
+    LOG.info("Valid num of txns: {}", currentValidTxnNum);
+    Assertions.assertEquals(25, currentValidTxnNum);
+
+    // reset the non-existing txns and valid txns, should do nothing
+    cmd.parseArgs("-l", "1,2,3,4,5,100,101,102,103,104,105");
+    resetCommand.execute(scmClient);
+    flush();
+    currentValidTxnNum = deletedBlockLog.getNumOfValidTransactions();
+    LOG.info("Valid num of txns: {}", currentValidTxnNum);
+    Assertions.assertEquals(25, currentValidTxnNum);
+
+    // reset all the result expired txIds, all transactions should be available
+    cmd.parseArgs("-a");
+    resetCommand.execute(scmClient);
+    flush();
+    currentValidTxnNum = deletedBlockLog.getNumOfValidTransactions();
+    LOG.info("Valid num of txns: {}", currentValidTxnNum);
+    Assertions.assertEquals(30, currentValidTxnNum);
+  }
+}
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestResetDeletedBlockRetryCountShell.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestResetDeletedBlockRetryCountShell.java
deleted file mode 100644
index 8e90864036..0000000000
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestResetDeletedBlockRetryCountShell.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * 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.conf.OzoneConfiguration;
-import org.apache.hadoop.hdds.scm.ScmConfigKeys;
-import org.apache.hadoop.hdds.scm.block.DeletedBlockLog;
-import org.apache.hadoop.hdds.scm.cli.ContainerOperationClient;
-import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
-import org.apache.hadoop.ozone.MiniOzoneCluster;
-import org.apache.hadoop.ozone.MiniOzoneHAClusterImpl;
-import org.apache.hadoop.ozone.admin.scm.ResetDeletedBlockRetryCountSubcommand;
-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 org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import picocli.CommandLine;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.UUID;
-import java.util.concurrent.TimeoutException;
-import java.util.stream.Collectors;
-
-import static 
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_BLOCK_DELETION_MAX_RETRY;
-
-/**
- * Test for ResetDeletedBlockRetryCountSubcommand Cli.
- */
-public class TestResetDeletedBlockRetryCountShell {
-
-  private static final Logger LOG = LoggerFactory
-      .getLogger(TestResetDeletedBlockRetryCountShell.class);
-  private MiniOzoneHAClusterImpl cluster = null;
-  private OzoneConfiguration conf;
-  private String clusterId;
-  private String scmId;
-  private String scmServiceId;
-  private int numOfSCMs = 3;
-
-  /**
-   * Create a MiniOzoneHACluster for testing.
-   *
-   * @throws IOException
-   */
-  @BeforeEach
-  public void init() throws Exception {
-    conf = new OzoneConfiguration();
-    clusterId = UUID.randomUUID().toString();
-    scmId = UUID.randomUUID().toString();
-    scmServiceId = "scm-service-test1";
-
-    conf.setBoolean(ScmConfigKeys.OZONE_SCM_HA_ENABLE_KEY, true);
-    conf.setInt(OZONE_SCM_BLOCK_DELETION_MAX_RETRY, 20);
-
-    cluster = (MiniOzoneHAClusterImpl) MiniOzoneCluster.newOMHABuilder(conf)
-        .setClusterId(clusterId)
-        .setScmId(scmId)
-        .setSCMServiceId(scmServiceId)
-        .setNumOfStorageContainerManagers(numOfSCMs)
-        .setNumOfActiveSCMs(numOfSCMs)
-        .setNumOfOzoneManagers(1)
-        .build();
-    cluster.waitForClusterToBeReady();
-  }
-
-  /**
-   * Shutdown MiniDFSCluster.
-   */
-  @AfterEach
-  public void shutdown() {
-    if (cluster != null) {
-      cluster.shutdown();
-    }
-  }
-
-  //<containerID,  List<blockID>>
-  private Map<Long, List<Long>> generateData(int dataSize) {
-    Map<Long, List<Long>> blockMap = new HashMap<>();
-    Random random = new Random(1);
-    int continerIDBase = random.nextInt(100);
-    int localIDBase = random.nextInt(1000);
-    for (int i = 0; i < dataSize; i++) {
-      long containerID = continerIDBase + i;
-      List<Long> blocks = new ArrayList<>();
-      for (int j = 0; j < 5; j++)  {
-        long localID = localIDBase + j;
-        blocks.add(localID);
-      }
-      blockMap.put(containerID, blocks);
-    }
-    return blockMap;
-  }
-
-  private StorageContainerManager getSCMLeader() {
-    return cluster.getStorageContainerManagersList()
-        .stream().filter(a -> a.getScmContext().isLeaderReady())
-        .collect(Collectors.toList()).get(0);
-  }
-
-  @Test
-  public void testResetCmd() throws IOException, TimeoutException {
-    int maxRetry = conf.getInt(OZONE_SCM_BLOCK_DELETION_MAX_RETRY, 20);
-    // add some block deletion transactions
-    DeletedBlockLog deletedBlockLog = getSCMLeader().
-        getScmBlockManager().getDeletedBlockLog();
-    deletedBlockLog.addTransactions(generateData(30));
-    getSCMLeader().getScmHAManager().asSCMHADBTransactionBuffer().flush();
-    LOG.info("Valid num of txns: {}", deletedBlockLog.
-        getNumOfValidTransactions());
-    Assertions.assertEquals(30, deletedBlockLog.getNumOfValidTransactions());
-
-    List<Long> txIds = new ArrayList<>();
-    for (int i = 1; i < 31; i++) {
-      txIds.add((long) i);
-    }
-    // increment retry count than threshold, count will be set to -1
-    for (int i = 0; i < maxRetry + 1; i++) {
-      deletedBlockLog.incrementCount(txIds);
-    }
-    for (StorageContainerManager scm:
-        cluster.getStorageContainerManagersList()) {
-      scm.getScmHAManager().asSCMHADBTransactionBuffer().flush();
-    }
-    LOG.info("Valid num of txns: {}", deletedBlockLog.
-        getNumOfValidTransactions());
-    Assertions.assertEquals(0, deletedBlockLog.getNumOfValidTransactions());
-
-    ResetDeletedBlockRetryCountSubcommand subcommand =
-        new ResetDeletedBlockRetryCountSubcommand();
-    ContainerOperationClient scmClient = new ContainerOperationClient(conf);
-    CommandLine c = new CommandLine(subcommand);
-    // reset the given txIds list, only these transactions should be available
-    c.parseArgs("-l", "1,2,3,4,5");
-    subcommand.execute(scmClient);
-    getSCMLeader().getScmHAManager().asSCMHADBTransactionBuffer().flush();
-    LOG.info("Valid num of txns: {}", deletedBlockLog.
-        getNumOfValidTransactions());
-    Assertions.assertEquals(5, deletedBlockLog.getNumOfValidTransactions());
-
-    // reset all the result expired txIds, all transactions should be available
-    c.parseArgs("-a");
-    subcommand.execute(scmClient);
-    getSCMLeader().getScmHAManager().asSCMHADBTransactionBuffer().flush();
-    LOG.info("Valid num of txns: {}", deletedBlockLog.
-        getNumOfValidTransactions());
-    Assertions.assertEquals(30, deletedBlockLog.getNumOfValidTransactions());
-  }
-}
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ScmAdmin.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/DeletedBlocksTxnCommands.java
similarity index 59%
copy from 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ScmAdmin.java
copy to 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/DeletedBlocksTxnCommands.java
index 34cf3c7011..3473cd8d03 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ScmAdmin.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/DeletedBlocksTxnCommands.java
@@ -19,50 +19,31 @@ package org.apache.hadoop.ozone.admin.scm;
 
 import org.apache.hadoop.hdds.cli.GenericCli;
 import org.apache.hadoop.hdds.cli.HddsVersionProvider;
-import org.apache.hadoop.hdds.cli.OzoneAdmin;
-import org.apache.hadoop.hdds.cli.SubcommandWithParent;
-import org.kohsuke.MetaInfServices;
 import picocli.CommandLine;
-import picocli.CommandLine.Model.CommandSpec;
-import picocli.CommandLine.Spec;
+
+import java.util.concurrent.Callable;
 
 /**
- * Subcommand for admin operations related to SCM.
+ * Subcommand to group container related operations.
  */
 @CommandLine.Command(
-    name = "scm",
-    description = "Ozone Storage Container Manager specific admin operations",
+    name = "deletedBlocksTxn",
+    description = "SCM deleted blocks transaction specific operations",
     mixinStandardHelpOptions = true,
     versionProvider = HddsVersionProvider.class,
     subcommands = {
-        GetScmRatisRolesSubcommand.class,
-        FinalizeScmUpgradeSubcommand.class,
-        FinalizationScmStatusSubcommand.class,
+        GetFailedDeletedBlocksTxnSubcommand.class,
         ResetDeletedBlockRetryCountSubcommand.class,
-        TransferScmLeaderSubCommand.class
     })
-@MetaInfServices(SubcommandWithParent.class)
-public class ScmAdmin extends GenericCli implements SubcommandWithParent {
-
-  @CommandLine.ParentCommand
-  private OzoneAdmin parent;
+public class DeletedBlocksTxnCommands implements Callable<Void> {
 
-  @Spec
-  private CommandSpec spec;
-
-  public OzoneAdmin getParent() {
-    return parent;
-  }
+  @CommandLine.Spec
+  private CommandLine.Model.CommandSpec spec;
 
   @Override
   public Void call() throws Exception {
     GenericCli.missingSubcommand(spec);
     return null;
   }
-
-  @Override
-  public Class<?> getParentType() {
-    return OzoneAdmin.class;
-  }
-
 }
+
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/GetFailedDeletedBlocksTxnSubcommand.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/GetFailedDeletedBlocksTxnSubcommand.java
new file mode 100644
index 0000000000..cad6c6c1a4
--- /dev/null
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/GetFailedDeletedBlocksTxnSubcommand.java
@@ -0,0 +1,91 @@
+/*
+ * 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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  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.scm;
+
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionInfo;
+import org.apache.hadoop.hdds.scm.cli.ScmSubcommand;
+import org.apache.hadoop.hdds.scm.client.ScmClient;
+import 
org.apache.hadoop.hdds.scm.container.common.helpers.DeletedBlocksTransactionInfoWrapper;
+import org.apache.hadoop.hdds.server.JsonUtils;
+import picocli.CommandLine;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Handler of getting expired deleted blocks from SCM side.
+ */
[email protected](
+    name = "ls",
+    description = "Print the failed DeletedBlocksTransaction(retry count = 
-1)",
+    mixinStandardHelpOptions = true,
+    versionProvider = HddsVersionProvider.class)
+public class GetFailedDeletedBlocksTxnSubcommand extends ScmSubcommand {
+
+  @CommandLine.ArgGroup(multiplicity = "1")
+  private TransactionsOption group;
+
+  static class TransactionsOption {
+    @CommandLine.Option(names = {"-a", "--all"},
+        description = "Get all the failed transactions.")
+    private boolean getAll;
+
+    @CommandLine.Option(names = {"-c", "--count"},
+        defaultValue = "20",
+        description = "Get at most the count number of the" +
+            " failed transactions.")
+    private int count;
+  }
+
+  @CommandLine.Option(names = {"-s", "--startTxId"},
+      defaultValue = "0",
+      description = "The least transaction ID to start with, default 0." +
+          " Only work with -c/--count")
+  private long startTxId;
+
+  @CommandLine.Option(names = {"-o", "--out"},
+      description = "Print transactions into file in JSON format.")
+  private String fileName;
+
+  private static final int LIST_ALL_FAILED_TRANSACTIONS = -1;
+
+  @Override
+  public void execute(ScmClient client) throws IOException {
+    List<DeletedBlocksTransactionInfo> response;
+    int count = group.getAll ? LIST_ALL_FAILED_TRANSACTIONS : group.count;
+    response = client.getFailedDeletedBlockTxn(count, startTxId);
+    List<DeletedBlocksTransactionInfoWrapper> txns = response.stream()
+        .map(DeletedBlocksTransactionInfoWrapper::fromProtobuf)
+        .filter(Objects::nonNull)
+        .collect(Collectors.toList());
+
+    String result = JsonUtils.toJsonStringWithDefaultPrettyPrinter(txns);
+    if (fileName != null) {
+      try (FileOutputStream f = new FileOutputStream(fileName)) {
+        f.write(result.getBytes(StandardCharsets.UTF_8));
+      }
+    } else {
+      System.out.println(result);
+    }
+  }
+}
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ResetDeletedBlockRetryCountSubcommand.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ResetDeletedBlockRetryCountSubcommand.java
index 7c91359e48..47a0ec2299 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ResetDeletedBlockRetryCountSubcommand.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ResetDeletedBlockRetryCountSubcommand.java
@@ -16,22 +16,32 @@
  */
 package org.apache.hadoop.ozone.admin.scm;
 
+import com.google.gson.Gson;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonSyntaxException;
 import org.apache.hadoop.hdds.cli.HddsVersionProvider;
 import org.apache.hadoop.hdds.scm.cli.ScmSubcommand;
 import org.apache.hadoop.hdds.scm.client.ScmClient;
+import 
org.apache.hadoop.hdds.scm.container.common.helpers.DeletedBlocksTransactionInfoWrapper;
 import picocli.CommandLine;
 
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
-
+import java.util.stream.Collectors;
 
 /**
- * Handler of the expired deleted blocks from SCM side.
+ * Handler of resetting expired deleted blocks from SCM side.
  */
 @CommandLine.Command(
-    name = "resetDeletedBlockRetryCount",
-    description = "Reset deleted block transactions whose retry count is -1",
+    name = "reset",
+    description = "Reset the retry count of failed DeletedBlocksTransaction",
     mixinStandardHelpOptions = true,
     versionProvider = HddsVersionProvider.class)
 public class ResetDeletedBlockRetryCountSubcommand extends ScmSubcommand {
@@ -41,25 +51,52 @@ public class ResetDeletedBlockRetryCountSubcommand extends 
ScmSubcommand {
 
   static class TransactionsOption {
     @CommandLine.Option(names = {"-a", "--all"},
-        description = "reset all expired deleted block transaction retry" +
+        description = "Reset all expired deleted block transaction retry" +
             " count from -1 to 0.")
     private boolean resetAll;
 
     @CommandLine.Option(names = {"-l", "--list"},
         split = ",",
-        description = "reset the only given deletedBlock transaction ID" +
-            " list, e.g 100,101,102.(Separated by ',')")
+        paramLabel = "txID",
+        description = "Reset the only given deletedBlock transaction ID" +
+            " list. Example: 100,101,102.(Separated by ',')")
     private List<Long> txList;
-  }
 
-  @CommandLine.ParentCommand
-  private ScmAdmin parent;
+    @CommandLine.Option(names = {"-i", "--in"},
+        description = "Use file as input, need to be JSON Array format and " +
+            "contains multi \"txID\" key. Example: 
[{\"txID\":1},{\"txID\":2}]")
+    private String fileName;
+  }
 
   @Override
   public void execute(ScmClient client) throws IOException {
     int count;
     if (group.resetAll) {
       count = client.resetDeletedBlockRetryCount(new ArrayList<>());
+    } else if (group.fileName != null) {
+      Gson gson = new Gson();
+      List<Long> txIDs;
+      try (InputStream in = new FileInputStream(group.fileName);
+           Reader fileReader = new InputStreamReader(in,
+               StandardCharsets.UTF_8)) {
+        DeletedBlocksTransactionInfoWrapper[] txns = gson.fromJson(fileReader,
+            DeletedBlocksTransactionInfoWrapper[].class);
+        txIDs = Arrays.stream(txns)
+            .map(DeletedBlocksTransactionInfoWrapper::getTxID)
+            .sorted()
+            .distinct()
+            .collect(Collectors.toList());
+        System.out.println("Num of loaded txIDs: " + txIDs.size());
+        if (!txIDs.isEmpty()) {
+          System.out.println("The first loaded txID: " + txIDs.get(0));
+          System.out.println("The last loaded txID: " +
+              txIDs.get(txIDs.size() - 1));
+        }
+      } catch (JsonIOException | JsonSyntaxException | IOException ex) {
+        System.out.println("Cannot parse the file " + group.fileName);
+        throw new IOException(ex);
+      }
+      count = client.resetDeletedBlockRetryCount(txIDs);
     } else {
       if (group.txList == null || group.txList.isEmpty()) {
         System.out.println("TransactionId list should not be empty");
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ScmAdmin.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ScmAdmin.java
index 34cf3c7011..a7f96dee84 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ScmAdmin.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/scm/ScmAdmin.java
@@ -38,8 +38,8 @@ import picocli.CommandLine.Spec;
         GetScmRatisRolesSubcommand.class,
         FinalizeScmUpgradeSubcommand.class,
         FinalizationScmStatusSubcommand.class,
-        ResetDeletedBlockRetryCountSubcommand.class,
-        TransferScmLeaderSubCommand.class
+        TransferScmLeaderSubCommand.class,
+        DeletedBlocksTxnCommands.class
     })
 @MetaInfServices(SubcommandWithParent.class)
 public class ScmAdmin extends GenericCli implements SubcommandWithParent {


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

Reply via email to