You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by sw...@apache.org on 2024/09/20 20:26:44 UTC

(ozone) branch master updated: HDDS-11408. Snapshot rename table entries are propagated incorrectly on snapshot deletes (#7200)

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

swamirishi 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 8c0b54ef0c HDDS-11408. Snapshot rename table entries are propagated incorrectly on snapshot deletes (#7200)
8c0b54ef0c is described below

commit 8c0b54ef0c661cc4c80f98e34a6615f1e5855761
Author: Swaminathan Balachandran <47...@users.noreply.github.com>
AuthorDate: Fri Sep 20 13:26:38 2024 -0700

    HDDS-11408. Snapshot rename table entries are propagated incorrectly on snapshot deletes (#7200)
---
 .../org/apache/hadoop/hdds/utils/db/Table.java     |  19 +
 .../main/java/org/apache/hadoop/ozone/OmUtils.java |   1 +
 ...estSnapshotDeletingServiceIntegrationTest.java} | 379 +++++++++++++--
 .../src/main/proto/OmClientProtocol.proto          |   9 +
 .../apache/hadoop/ozone/om/OMMetadataManager.java  |  16 +
 .../org/apache/hadoop/ozone/om/KeyManager.java     |  48 +-
 .../org/apache/hadoop/ozone/om/KeyManagerImpl.java |  77 ++-
 .../hadoop/ozone/om/OmMetadataManagerImpl.java     |  18 +-
 .../hadoop/ozone/om/SnapshotChainManager.java      |  45 +-
 .../om/ratis/utils/OzoneManagerRatisUtils.java     |   3 +
 .../snapshot/OMSnapshotMoveDeletedKeysRequest.java |   2 +-
 .../snapshot/OMSnapshotMoveTableKeysRequest.java   | 184 +++++++
 .../request/snapshot/OMSnapshotPurgeRequest.java   |   2 +-
 .../OMSnapshotMoveDeletedKeysResponse.java         |  35 +-
 .../snapshot/OMSnapshotMoveTableKeysResponse.java  | 162 +++++++
 .../ozone/om/service/DirectoryDeletingService.java |  21 +-
 .../ozone/om/service/KeyDeletingService.java       |  22 +-
 .../ozone/om/service/SnapshotDeletingService.java  | 526 ++++++---------------
 .../hadoop/ozone/om/snapshot/SnapshotUtils.java    |  80 +++-
 .../ozone/om/request/OMRequestTestUtils.java       |  81 +++-
 .../snapshot/TestOMSnapshotCreateRequest.java      | 172 +++----
 .../snapshot/TestOMSnapshotDeleteRequest.java      | 157 ++----
 .../TestOMSnapshotMoveTableKeysRequest.java        | 264 +++++++++++
 .../TestOMSnapshotPurgeRequestAndResponse.java     | 189 ++------
 .../snapshot/TestOMSnapshotRenameRequest.java      | 174 +++----
 ...estOMSnapshotSetPropertyRequestAndResponse.java |  86 +---
 .../TestOMSnapshotMoveTableKeysResponse.java       | 199 ++++++++
 .../om/service/TestSnapshotDeletingService.java    |  38 +-
 .../ozone/om/snapshot/TestSnapshotChain.java       |  32 ++
 .../snapshot/TestSnapshotRequestAndResponse.java   | 255 ++++++++++
 30 files changed, 2237 insertions(+), 1059 deletions(-)

diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/Table.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/Table.java
index c818c07b1a..f153823db7 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/Table.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/db/Table.java
@@ -24,6 +24,7 @@ import java.io.IOException;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 import org.apache.commons.lang3.NotImplementedException;
 import org.apache.hadoop.hdds.annotation.InterfaceStability;
@@ -354,6 +355,24 @@ public interface Table<KEY, VALUE> extends AutoCloseable {
       public String toString() {
         return "(key=" + key + ", value=" + value + ")";
       }
+
+      @Override
+      public boolean equals(Object obj) {
+        if (!(obj instanceof KeyValue)) {
+          return false;
+        }
+        KeyValue<?, ?> kv = (KeyValue<?, ?>) obj;
+        try {
+          return getKey().equals(kv.getKey()) && getValue().equals(kv.getValue());
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(getKey(), getValue());
+      }
     };
   }
 
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 8fa8921cc9..b70ea51fde 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
@@ -333,6 +333,7 @@ public final class OmUtils {
     case DeleteSnapshot:
     case RenameSnapshot:
     case SnapshotMoveDeletedKeys:
+    case SnapshotMoveTableKeys:
     case SnapshotPurge:
     case RecoverLease:
     case SetTimes:
diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDeletingService.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDeletingServiceIntegrationTest.java
similarity index 52%
rename from hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDeletingService.java
rename to hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDeletingServiceIntegrationTest.java
index be4ea69095..d77f9bf9d8 100644
--- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDeletingService.java
+++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotDeletingServiceIntegrationTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.hadoop.ozone.om.snapshot;
 
+import org.apache.commons.compress.utils.Lists;
 import org.apache.hadoop.hdds.client.ReplicationFactor;
 import org.apache.hadoop.hdds.client.ReplicationType;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
@@ -32,20 +33,26 @@ import org.apache.hadoop.ozone.TestDataUtil;
 import org.apache.hadoop.ozone.client.BucketArgs;
 import org.apache.hadoop.ozone.client.OzoneBucket;
 import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.om.KeyManager;
 import org.apache.hadoop.ozone.om.OMConfigKeys;
 import org.apache.hadoop.ozone.om.OMMetadataManager;
 import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
 import org.apache.hadoop.ozone.om.OmSnapshot;
+import org.apache.hadoop.ozone.om.OmSnapshotManager;
 import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.SnapshotChainManager;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
 import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo;
 import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
+import org.apache.hadoop.ozone.om.service.DirectoryDeletingService;
+import org.apache.hadoop.ozone.om.service.KeyDeletingService;
 import org.apache.hadoop.ozone.om.service.SnapshotDeletingService;
 import org.apache.ozone.test.GenericTestUtils;
 import org.apache.ozone.test.tag.Flaky;
 import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
 import org.junit.jupiter.api.Order;
@@ -53,17 +60,27 @@ import org.junit.jupiter.api.TestMethodOrder;
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
+import org.mockito.Mockito;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.Random;
 import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
 
 import static org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_CHUNK_SIZE_KEY;
 import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED;
@@ -72,6 +89,11 @@ import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SNAPSHOT_DELETING_SE
 import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SNAPSHOT_DELETING_SERVICE_TIMEOUT;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
 
 /**
  * Test Snapshot Deleting Service.
@@ -80,10 +102,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 @Timeout(300)
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 @TestMethodOrder(OrderAnnotation.class)
-public class TestSnapshotDeletingService {
+public class TestSnapshotDeletingServiceIntegrationTest {
 
   private static final Logger LOG =
-      LoggerFactory.getLogger(TestSnapshotDeletingService.class);
+      LoggerFactory.getLogger(TestSnapshotDeletingServiceIntegrationTest.class);
   private static boolean omRatisEnabled = true;
   private static final ByteBuffer CONTENT =
       ByteBuffer.allocate(1024 * 1024 * 16);
@@ -147,7 +169,7 @@ public class TestSnapshotDeletingService {
       Table<String, SnapshotInfo> snapshotInfoTable =
           om.getMetadataManager().getSnapshotInfoTable();
 
-      createSnapshotDataForBucket1();
+      createSnapshotDataForBucket(bucket1);
 
       assertTableRowCount(snapshotInfoTable, 2);
       GenericTestUtils.waitFor(() -> snapshotDeletingService
@@ -174,7 +196,7 @@ public class TestSnapshotDeletingService {
         om.getMetadataManager().getSnapshotInfoTable();
     runIndividualTest = false;
 
-    createSnapshotDataForBucket1();
+    createSnapshotDataForBucket(bucket1);
 
     BucketArgs bucketArgs = new BucketArgs.Builder()
         .setBucketLayout(BucketLayout.LEGACY)
@@ -454,6 +476,228 @@ public class TestSnapshotDeletingService {
     rcSnap1.close();
   }
 
+  private DirectoryDeletingService getMockedDirectoryDeletingService(AtomicBoolean dirDeletionWaitStarted,
+                                                                     AtomicBoolean dirDeletionStarted)
+      throws InterruptedException, TimeoutException {
+    OzoneManager ozoneManager = Mockito.spy(om);
+    om.getKeyManager().getDirDeletingService().shutdown();
+    GenericTestUtils.waitFor(() -> om.getKeyManager().getDirDeletingService().getThreadCount() == 0, 1000,
+        100000);
+    DirectoryDeletingService directoryDeletingService = Mockito.spy(new DirectoryDeletingService(10000,
+        TimeUnit.MILLISECONDS, 100000, ozoneManager, cluster.getConf()));
+    directoryDeletingService.shutdown();
+    GenericTestUtils.waitFor(() -> directoryDeletingService.getThreadCount() == 0, 1000,
+        100000);
+    when(ozoneManager.getMetadataManager()).thenAnswer(i -> {
+      // Wait for SDS to reach DDS wait block before processing any deleted directories.
+      GenericTestUtils.waitFor(dirDeletionWaitStarted::get, 1000, 100000);
+      dirDeletionStarted.set(true);
+      return i.callRealMethod();
+    });
+    return directoryDeletingService;
+  }
+
+  private KeyDeletingService getMockedKeyDeletingService(AtomicBoolean keyDeletionWaitStarted,
+                                                         AtomicBoolean keyDeletionStarted)
+      throws InterruptedException, TimeoutException, IOException {
+    OzoneManager ozoneManager = Mockito.spy(om);
+    om.getKeyManager().getDeletingService().shutdown();
+    GenericTestUtils.waitFor(() -> om.getKeyManager().getDeletingService().getThreadCount() == 0, 1000,
+        100000);
+    KeyManager keyManager = Mockito.spy(om.getKeyManager());
+    when(ozoneManager.getKeyManager()).thenReturn(keyManager);
+    KeyDeletingService keyDeletingService = Mockito.spy(new KeyDeletingService(ozoneManager,
+        ozoneManager.getScmClient().getBlockClient(), keyManager, 10000,
+        100000, cluster.getConf()));
+    keyDeletingService.shutdown();
+    GenericTestUtils.waitFor(() -> keyDeletingService.getThreadCount() == 0, 1000,
+        100000);
+    when(keyManager.getPendingDeletionKeys(anyInt())).thenAnswer(i -> {
+      // wait for SDS to reach the KDS wait block before processing any key.
+      GenericTestUtils.waitFor(keyDeletionWaitStarted::get, 1000, 100000);
+      keyDeletionStarted.set(true);
+      return i.callRealMethod();
+    });
+    return keyDeletingService;
+  }
+
+  @SuppressWarnings("checkstyle:parameternumber")
+  private SnapshotDeletingService getMockedSnapshotDeletingService(KeyDeletingService keyDeletingService,
+                                                                   DirectoryDeletingService directoryDeletingService,
+                                                                   AtomicBoolean snapshotDeletionStarted,
+                                                                   AtomicBoolean keyDeletionWaitStarted,
+                                                                   AtomicBoolean dirDeletionWaitStarted,
+                                                                   AtomicBoolean keyDeletionStarted,
+                                                                   AtomicBoolean dirDeletionStarted,
+                                                                   OzoneBucket testBucket)
+      throws InterruptedException, TimeoutException, IOException {
+    OzoneManager ozoneManager = Mockito.spy(om);
+    om.getKeyManager().getSnapshotDeletingService().shutdown();
+    GenericTestUtils.waitFor(() -> om.getKeyManager().getSnapshotDeletingService().getThreadCount() == 0, 1000,
+        100000);
+    KeyManager keyManager = Mockito.spy(om.getKeyManager());
+    OmMetadataManagerImpl omMetadataManager = Mockito.spy((OmMetadataManagerImpl)om.getMetadataManager());
+    SnapshotChainManager unMockedSnapshotChainManager =
+        ((OmMetadataManagerImpl)om.getMetadataManager()).getSnapshotChainManager();
+    SnapshotChainManager snapshotChainManager = Mockito.spy(unMockedSnapshotChainManager);
+    OmSnapshotManager omSnapshotManager = Mockito.spy(om.getOmSnapshotManager());
+    when(ozoneManager.getOmSnapshotManager()).thenReturn(omSnapshotManager);
+    when(ozoneManager.getKeyManager()).thenReturn(keyManager);
+    when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
+    when(omMetadataManager.getSnapshotChainManager()).thenReturn(snapshotChainManager);
+    when(keyManager.getDeletingService()).thenReturn(keyDeletingService);
+    when(keyManager.getDirDeletingService()).thenReturn(directoryDeletingService);
+    SnapshotDeletingService snapshotDeletingService = Mockito.spy(new SnapshotDeletingService(10000,
+        100000, ozoneManager));
+    snapshotDeletingService.shutdown();
+    GenericTestUtils.waitFor(() -> snapshotDeletingService.getThreadCount() == 0, 1000,
+        100000);
+    when(snapshotChainManager.iterator(anyBoolean())).thenAnswer(i -> {
+      Iterator<UUID> itr = (Iterator<UUID>) i.callRealMethod();
+      return Lists.newArrayList(itr).stream().filter(uuid -> {
+        try {
+          SnapshotInfo snapshotInfo = SnapshotUtils.getSnapshotInfo(om, snapshotChainManager, uuid);
+          return snapshotInfo.getBucketName().equals(testBucket.getName()) &&
+              snapshotInfo.getVolumeName().equals(testBucket.getVolumeName());
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }).iterator();
+    });
+    when(snapshotChainManager.getLatestGlobalSnapshotId())
+        .thenAnswer(i -> unMockedSnapshotChainManager.getLatestGlobalSnapshotId());
+    when(snapshotChainManager.getOldestGlobalSnapshotId())
+        .thenAnswer(i -> unMockedSnapshotChainManager.getOldestGlobalSnapshotId());
+    doAnswer(i -> {
+      // KDS wait block reached in SDS.
+      GenericTestUtils.waitFor(() -> {
+        return keyDeletingService.isRunningOnAOS();
+      }, 1000, 100000);
+      keyDeletionWaitStarted.set(true);
+      return i.callRealMethod();
+    }).when(snapshotDeletingService).waitForKeyDeletingService();
+    doAnswer(i -> {
+      // DDS wait block reached in SDS.
+      GenericTestUtils.waitFor(directoryDeletingService::isRunningOnAOS, 1000, 100000);
+      dirDeletionWaitStarted.set(true);
+      return i.callRealMethod();
+    }).when(snapshotDeletingService).waitForDirDeletingService();
+    doAnswer(i -> {
+      // Assert KDS & DDS is not running when SDS starts moving entries & assert all wait block, KDS processing
+      // AOS block & DDS AOS block have been executed.
+      Assertions.assertTrue(keyDeletionWaitStarted.get());
+      Assertions.assertTrue(dirDeletionWaitStarted.get());
+      Assertions.assertTrue(keyDeletionStarted.get());
+      Assertions.assertTrue(dirDeletionStarted.get());
+      Assertions.assertFalse(keyDeletingService.isRunningOnAOS());
+      Assertions.assertFalse(directoryDeletingService.isRunningOnAOS());
+      snapshotDeletionStarted.set(true);
+      return i.callRealMethod();
+    }).when(omSnapshotManager).getSnapshot(anyString(), anyString(), anyString());
+    return snapshotDeletingService;
+  }
+
+  @Test
+  @Order(4)
+  public void testParallelExcecutionOfKeyDeletionAndSnapshotDeletion() throws Exception {
+    AtomicBoolean keyDeletionWaitStarted = new AtomicBoolean(false);
+    AtomicBoolean dirDeletionWaitStarted = new AtomicBoolean(false);
+    AtomicBoolean keyDeletionStarted = new AtomicBoolean(false);
+    AtomicBoolean dirDeletionStarted = new AtomicBoolean(false);
+    AtomicBoolean snapshotDeletionStarted = new AtomicBoolean(false);
+    Random random = new Random();
+    String bucketName = "bucket" + random.nextInt();
+    BucketArgs bucketArgs = new BucketArgs.Builder()
+        .setBucketLayout(BucketLayout.FILE_SYSTEM_OPTIMIZED)
+        .build();
+    OzoneBucket testBucket = TestDataUtil.createBucket(
+        client, VOLUME_NAME, bucketArgs, bucketName);
+    // mock keyDeletingService
+    KeyDeletingService keyDeletingService = getMockedKeyDeletingService(keyDeletionWaitStarted, keyDeletionStarted);
+
+    // mock dirDeletingService
+    DirectoryDeletingService directoryDeletingService = getMockedDirectoryDeletingService(dirDeletionWaitStarted,
+        dirDeletionStarted);
+
+    // mock snapshotDeletingService.
+    SnapshotDeletingService snapshotDeletingService = getMockedSnapshotDeletingService(keyDeletingService,
+        directoryDeletingService, snapshotDeletionStarted, keyDeletionWaitStarted, dirDeletionWaitStarted,
+        keyDeletionStarted, dirDeletionStarted, testBucket);
+    createSnapshotFSODataForBucket(testBucket);
+    List<Table.KeyValue<String, String>> renamesKeyEntries;
+    List<Table.KeyValue<String, List<OmKeyInfo>>> deletedKeyEntries;
+    List<Table.KeyValue<String, OmKeyInfo>> deletedDirEntries;
+    try (ReferenceCounted<OmSnapshot> snapshot = om.getOmSnapshotManager().getSnapshot(testBucket.getVolumeName(),
+        testBucket.getName(), testBucket.getName() + "snap2")) {
+      renamesKeyEntries = snapshot.get().getKeyManager().getRenamesKeyEntries(testBucket.getVolumeName(),
+          testBucket.getName(), "", 1000);
+      deletedKeyEntries = snapshot.get().getKeyManager().getDeletedKeyEntries(testBucket.getVolumeName(),
+          testBucket.getName(), "", 1000);
+      deletedDirEntries = snapshot.get().getKeyManager().getDeletedDirEntries(testBucket.getVolumeName(),
+          testBucket.getName(), 1000);
+    }
+    Thread keyDeletingThread = new Thread(() -> {
+      try {
+        keyDeletingService.runPeriodicalTaskNow();
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    });
+    Thread directoryDeletingThread = new Thread(() -> {
+      try {
+        directoryDeletingService.runPeriodicalTaskNow();
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    });
+    ExecutorService snapshotDeletingThread = Executors.newFixedThreadPool(1);
+    Runnable snapshotDeletionRunnable = () -> {
+      try {
+        snapshotDeletingService.runPeriodicalTaskNow();
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    };
+    keyDeletingThread.start();
+    directoryDeletingThread.start();
+    Future<?> future = snapshotDeletingThread.submit(snapshotDeletionRunnable);
+    GenericTestUtils.waitFor(snapshotDeletionStarted::get, 1000, 30000);
+    future.get();
+    try (ReferenceCounted<OmSnapshot> snapshot = om.getOmSnapshotManager().getSnapshot(testBucket.getVolumeName(),
+        testBucket.getName(), testBucket.getName() + "snap2")) {
+      Assertions.assertEquals(Collections.emptyList(),
+          snapshot.get().getKeyManager().getRenamesKeyEntries(testBucket.getVolumeName(),
+          testBucket.getName(), "", 1000));
+      Assertions.assertEquals(Collections.emptyList(),
+          snapshot.get().getKeyManager().getDeletedKeyEntries(testBucket.getVolumeName(),
+          testBucket.getName(), "", 1000));
+      Assertions.assertEquals(Collections.emptyList(),
+          snapshot.get().getKeyManager().getDeletedDirEntries(testBucket.getVolumeName(),
+          testBucket.getName(), 1000));
+    }
+    List<Table.KeyValue<String, String>> aosRenamesKeyEntries =
+        om.getKeyManager().getRenamesKeyEntries(testBucket.getVolumeName(),
+            testBucket.getName(), "", 1000);
+    List<Table.KeyValue<String, List<OmKeyInfo>>> aosDeletedKeyEntries =
+        om.getKeyManager().getDeletedKeyEntries(testBucket.getVolumeName(),
+            testBucket.getName(), "", 1000);
+    List<Table.KeyValue<String, OmKeyInfo>> aosDeletedDirEntries =
+        om.getKeyManager().getDeletedDirEntries(testBucket.getVolumeName(),
+            testBucket.getName(), 1000);
+    renamesKeyEntries.forEach(entry -> Assertions.assertTrue(aosRenamesKeyEntries.contains(entry)));
+    deletedKeyEntries.forEach(entry -> Assertions.assertTrue(aosDeletedKeyEntries.contains(entry)));
+    deletedDirEntries.forEach(entry -> Assertions.assertTrue(aosDeletedDirEntries.contains(entry)));
+    Mockito.reset(snapshotDeletingService);
+    SnapshotInfo snap2 = SnapshotUtils.getSnapshotInfo(om, testBucket.getVolumeName(),
+        testBucket.getName(), testBucket.getName() + "snap2");
+    Assertions.assertEquals(snap2.getSnapshotStatus(), SnapshotInfo.SnapshotStatus.SNAPSHOT_DELETED);
+    future = snapshotDeletingThread.submit(snapshotDeletionRunnable);
+    future.get();
+    Assertions.assertThrows(IOException.class, () -> SnapshotUtils.getSnapshotInfo(om, testBucket.getVolumeName(),
+        testBucket.getName(), testBucket.getName() + "snap2"));
+    cluster.restartOzoneManager();
+  }
+
   /*
       Flow
       ----
@@ -472,7 +716,7 @@ public class TestSnapshotDeletingService {
       create snapshot3
       delete snapshot2
    */
-  private void createSnapshotDataForBucket1() throws Exception {
+  private synchronized void createSnapshotDataForBucket(OzoneBucket bucket) throws Exception {
     Table<String, SnapshotInfo> snapshotInfoTable =
         om.getMetadataManager().getSnapshotInfoTable();
     Table<String, RepeatedOmKeyInfo> deletedTable =
@@ -482,70 +726,147 @@ public class TestSnapshotDeletingService {
     OmMetadataManagerImpl metadataManager = (OmMetadataManagerImpl)
         om.getMetadataManager();
 
-    TestDataUtil.createKey(bucket1, "bucket1key0", ReplicationFactor.THREE,
+    TestDataUtil.createKey(bucket, bucket.getName() + "key0", ReplicationFactor.THREE,
         ReplicationType.RATIS, CONTENT);
-    TestDataUtil.createKey(bucket1, "bucket1key1", ReplicationFactor.THREE,
+    TestDataUtil.createKey(bucket, bucket.getName() + "key1", ReplicationFactor.THREE,
         ReplicationType.RATIS, CONTENT);
     assertTableRowCount(keyTable, 2);
 
     // Create Snapshot 1.
-    client.getProxy().createSnapshot(VOLUME_NAME, BUCKET_NAME_ONE,
-        "bucket1snap1");
+    client.getProxy().createSnapshot(bucket.getVolumeName(), bucket.getName(),
+        bucket.getName() + "snap1");
     assertTableRowCount(snapshotInfoTable, 1);
 
     // Overwrite bucket1key0, This is a newer version of the key which should
     // reclaimed as this is a different version of the key.
-    TestDataUtil.createKey(bucket1, "bucket1key0", ReplicationFactor.THREE,
+    TestDataUtil.createKey(bucket, bucket.getName() + "key0", ReplicationFactor.THREE,
         ReplicationType.RATIS, CONTENT);
-    TestDataUtil.createKey(bucket1, "bucket1key2", ReplicationFactor.THREE,
+    TestDataUtil.createKey(bucket, bucket.getName() + "key2", ReplicationFactor.THREE,
         ReplicationType.RATIS, CONTENT);
 
     // Key 1 cannot be reclaimed as it is still referenced by Snapshot 1.
-    client.getProxy().deleteKey(VOLUME_NAME, BUCKET_NAME_ONE,
-        "bucket1key1", false);
+    client.getProxy().deleteKey(bucket.getVolumeName(), bucket.getName(),
+        bucket.getName() + "key1", false);
     // Key 2 is deleted here, which will be reclaimed here as
     // it is not being referenced by previous snapshot.
-    client.getProxy().deleteKey(VOLUME_NAME, BUCKET_NAME_ONE,
-        "bucket1key2", false);
-    client.getProxy().deleteKey(VOLUME_NAME, BUCKET_NAME_ONE,
-        "bucket1key0", false);
+    client.getProxy().deleteKey(bucket.getVolumeName(), bucket.getName(),
+        bucket.getName() + "key2", false);
+    client.getProxy().deleteKey(bucket.getVolumeName(), bucket.getName(),
+        bucket.getName() + "key0", false);
     assertTableRowCount(keyTable, 0);
     // one copy of bucket1key0 should also be reclaimed as it not same
     // but original deleted key created during overwrite should not be deleted
     assertTableRowCount(deletedTable, 2);
 
     // Create Snapshot 2.
-    client.getProxy().createSnapshot(VOLUME_NAME, BUCKET_NAME_ONE,
-        "bucket1snap2");
+    client.getProxy().createSnapshot(bucket.getVolumeName(), bucket.getName(),
+        bucket.getName() + "snap2");
     assertTableRowCount(snapshotInfoTable, 2);
     // Key 2 is removed from the active Db's
     // deletedTable when Snapshot 2 is taken.
     assertTableRowCount(deletedTable, 0);
 
-    TestDataUtil.createKey(bucket1, "bucket1key3", ReplicationFactor.THREE,
+    TestDataUtil.createKey(bucket, bucket.getName() + "key3", ReplicationFactor.THREE,
         ReplicationType.RATIS, CONTENT);
-    TestDataUtil.createKey(bucket1, "bucket1key4", ReplicationFactor.THREE,
+    TestDataUtil.createKey(bucket, bucket.getName() + "key4", ReplicationFactor.THREE,
         ReplicationType.RATIS, CONTENT);
-    client.getProxy().deleteKey(VOLUME_NAME, BUCKET_NAME_ONE,
-        "bucket1key4", false);
+    client.getProxy().deleteKey(bucket.getVolumeName(), bucket.getName(),
+        bucket.getName() + "key4", false);
     assertTableRowCount(keyTable, 1);
     assertTableRowCount(deletedTable, 0);
 
     // Create Snapshot 3.
-    client.getProxy().createSnapshot(VOLUME_NAME, BUCKET_NAME_ONE,
-        "bucket1snap3");
+    client.getProxy().createSnapshot(bucket.getVolumeName(), bucket.getName(),
+        bucket.getName() + "snap3");
     assertTableRowCount(snapshotInfoTable, 3);
 
     SnapshotInfo snapshotInfo = metadataManager.getSnapshotInfoTable()
-        .get("/vol1/bucket1/bucket1snap2");
+        .get(String.format("/%s/%s/%ssnap2", bucket.getVolumeName(), bucket.getName(), bucket.getName()));
 
     // Delete Snapshot 2.
-    client.getProxy().deleteSnapshot(VOLUME_NAME, BUCKET_NAME_ONE,
-        "bucket1snap2");
+    client.getProxy().deleteSnapshot(bucket.getVolumeName(), bucket.getName(),
+        bucket.getName() + "snap2");
     assertTableRowCount(snapshotInfoTable, 2);
-    verifySnapshotChain(snapshotInfo, "/vol1/bucket1/bucket1snap3");
+    verifySnapshotChain(snapshotInfo, String.format("/%s/%s/%ssnap3", bucket.getVolumeName(), bucket.getName(),
+        bucket.getName()));
+  }
+
+
+  /*
+      Flow
+      ----
+      create dir0/key0
+      create dir1/key1
+      overwrite dir0/key0
+      create dir2/key2
+      create snap1
+      rename dir1/key1 -> dir1/key10
+      delete dir1/key10
+      delete dir2
+      create snap2
+      delete snap2
+   */
+  private synchronized void createSnapshotFSODataForBucket(OzoneBucket bucket) throws Exception {
+    Table<String, SnapshotInfo> snapshotInfoTable =
+        om.getMetadataManager().getSnapshotInfoTable();
+    Table<String, RepeatedOmKeyInfo> deletedTable =
+        om.getMetadataManager().getDeletedTable();
+    Table<String, OmKeyInfo> deletedDirTable =
+        om.getMetadataManager().getDeletedDirTable();
+    Table<String, OmKeyInfo> keyTable =
+        om.getMetadataManager().getKeyTable(BucketLayout.FILE_SYSTEM_OPTIMIZED);
+    Table<String, OmDirectoryInfo> dirTable =
+        om.getMetadataManager().getDirectoryTable();
+    Table<String, String> renameTable = om.getMetadataManager().getSnapshotRenamedTable();
+    OmMetadataManagerImpl metadataManager = (OmMetadataManagerImpl)
+        om.getMetadataManager();
+    Map<String, Integer> countMap =
+        metadataManager.listTables().entrySet().stream()
+            .collect(Collectors.toMap(Map.Entry::getKey, e -> {
+              try {
+                return (int)metadataManager.countRowsInTable(e.getValue());
+              } catch (IOException ex) {
+                throw new RuntimeException(ex);
+              }
+            }));
+    TestDataUtil.createKey(bucket, "dir0/" + bucket.getName() + "key0", ReplicationFactor.THREE,
+        ReplicationType.RATIS, CONTENT);
+    TestDataUtil.createKey(bucket, "dir1/" + bucket.getName() + "key1", ReplicationFactor.THREE,
+        ReplicationType.RATIS, CONTENT);
+    assertTableRowCount(keyTable, countMap.get(keyTable.getName()) + 2);
+    assertTableRowCount(dirTable, countMap.get(dirTable.getName()) + 2);
+
+    // Overwrite bucket1key0, This is a newer version of the key which should
+    // reclaimed as this is a different version of the key.
+    TestDataUtil.createKey(bucket, "dir0/" + bucket.getName() + "key0", ReplicationFactor.THREE,
+        ReplicationType.RATIS, CONTENT);
+    TestDataUtil.createKey(bucket, "dir2/" + bucket.getName() + "key2", ReplicationFactor.THREE,
+        ReplicationType.RATIS, CONTENT);
+    assertTableRowCount(keyTable, countMap.get(keyTable.getName()) + 3);
+    assertTableRowCount(dirTable, countMap.get(dirTable.getName()) + 3);
+    assertTableRowCount(deletedTable, countMap.get(deletedTable.getName()) + 1);
+    // create snap1
+    client.getProxy().createSnapshot(bucket.getVolumeName(), bucket.getName(),
+        bucket.getName() + "snap1");
+    bucket.renameKey("dir1/" + bucket.getName() + "key1", "dir1/" + bucket.getName() + "key10");
+    bucket.renameKey("dir1/", "dir10/");
+    assertTableRowCount(renameTable, countMap.get(renameTable.getName()) + 2);
+    client.getProxy().deleteKey(bucket.getVolumeName(), bucket.getName(),
+        "dir10/" + bucket.getName() + "key10", false);
+    assertTableRowCount(deletedTable, countMap.get(deletedTable.getName()) + 1);
+    // Key 2 is deleted here, which will be reclaimed here as
+    // it is not being referenced by previous snapshot.
+    client.getProxy().deleteKey(bucket.getVolumeName(), bucket.getName(), "dir2", true);
+    assertTableRowCount(deletedDirTable, countMap.get(deletedDirTable.getName()) + 1);
+    client.getProxy().createSnapshot(bucket.getVolumeName(), bucket.getName(),
+        bucket.getName() + "snap2");
+    // Delete Snapshot 2.
+    client.getProxy().deleteSnapshot(bucket.getVolumeName(), bucket.getName(),
+        bucket.getName() + "snap2");
+    assertTableRowCount(snapshotInfoTable, countMap.get(snapshotInfoTable.getName()) +  2);
   }
 
+
   private void verifySnapshotChain(SnapshotInfo deletedSnapshot,
                                    String nextSnapshot)
       throws Exception {
diff --git a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index eefcfa7552..126adbdc51 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -153,6 +153,7 @@ enum Type {
   GetServerDefaults = 134;
   GetQuotaRepairStatus = 135;
   StartQuotaRepair = 136;
+  SnapshotMoveTableKeys = 137;
 }
 
 enum SafeMode {
@@ -295,6 +296,7 @@ message OMRequest {
   optional ServerDefaultsRequest            ServerDefaultsRequest          = 132;
   optional GetQuotaRepairStatusRequest      GetQuotaRepairStatusRequest    = 133;
   optional StartQuotaRepairRequest          StartQuotaRepairRequest        = 134;
+  optional SnapshotMoveTableKeysRequest     SnapshotMoveTableKeysRequest   = 135;
 }
 
 message OMResponse {
@@ -1981,6 +1983,13 @@ message SnapshotMoveDeletedKeysRequest {
   repeated string deletedDirsToMove = 5;
 }
 
+message SnapshotMoveTableKeysRequest {
+  optional hadoop.hdds.UUID fromSnapshotID = 1;
+  repeated SnapshotMoveKeyInfos deletedKeys = 2;
+  repeated SnapshotMoveKeyInfos deletedDirs = 3;
+  repeated hadoop.hdds.KeyValue renamedKeys = 4;
+}
+
 message SnapshotMoveKeyInfos {
   optional string key = 1;
   repeated KeyInfo keyInfos = 2;
diff --git a/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java b/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java
index cf0819ca52..67f7ce2f07 100644
--- a/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java
+++ b/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java
@@ -116,6 +116,22 @@ public interface OMMetadataManager extends DBStoreHAManager {
    */
   String getBucketKey(String volume, String bucket);
 
+  /**
+   * Given a volume and bucket, return the corresponding DB key prefix.
+   *
+   * @param volume - Volume name
+   * @param bucket - Bucket name
+   */
+  String getBucketKeyPrefix(String volume, String bucket);
+
+  /**
+   * Given a volume and bucket, return the corresponding DB key prefix for FSO buckets.
+   *
+   * @param volume - Volume name
+   * @param bucket - Bucket name
+   */
+  String getBucketKeyPrefixFSO(String volume, String bucket) throws IOException;
+
   /**
    * Given a volume, bucket and a key, return the corresponding DB key.
    *
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManager.java
index b7fa5d746f..068ba9aa4a 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManager.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManager.java
@@ -18,6 +18,7 @@ package org.apache.hadoop.ozone.om;
 
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.hdds.utils.db.TableIterator;
 import org.apache.hadoop.ozone.common.BlockGroup;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
@@ -28,6 +29,7 @@ import org.apache.hadoop.ozone.om.helpers.OmMultipartUploadList;
 import org.apache.hadoop.ozone.om.helpers.OmMultipartUploadListParts;
 import org.apache.hadoop.ozone.om.fs.OzoneManagerFS;
 import org.apache.hadoop.hdds.utils.BackgroundService;
+import org.apache.hadoop.ozone.om.service.DirectoryDeletingService;
 import org.apache.hadoop.ozone.om.service.KeyDeletingService;
 import org.apache.hadoop.ozone.om.service.SnapshotDeletingService;
 import org.apache.hadoop.ozone.om.service.SnapshotDirectoryCleaningService;
@@ -35,6 +37,7 @@ import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Expired
 
 import java.io.IOException;
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -119,6 +122,29 @@ public interface KeyManager extends OzoneManagerFS, IOzoneAcl {
    */
   PendingKeysDeletion getPendingDeletionKeys(int count) throws IOException;
 
+  /**
+   * Returns a list rename entries from the snapshotRenamedTable.
+   *
+   * @param size max number of keys to return.
+   * @return a Pair of list of {@link org.apache.hadoop.hdds.utils.db.Table.KeyValue} representing the keys in the
+   * underlying metadataManager.
+   * @throws IOException
+   */
+  List<Table.KeyValue<String, String>> getRenamesKeyEntries(
+      String volume, String bucket, String startKey, int size) throws IOException;
+
+
+  /**
+   * Returns a list deleted entries from the deletedTable.
+   *
+   * @param size max number of keys to return.
+   * @return a Pair of list of {@link org.apache.hadoop.hdds.utils.db.Table.KeyValue} representing the keys in the
+   * underlying metadataManager.
+   * @throws IOException
+   */
+  List<Table.KeyValue<String, List<OmKeyInfo>>> getDeletedKeyEntries(
+      String volume, String bucket, String startKey, int size) throws IOException;
+
   /**
    * Returns the names of up to {@code count} open keys whose age is
    * greater than or equal to {@code expireThreshold}.
@@ -216,6 +242,26 @@ public interface KeyManager extends OzoneManagerFS, IOzoneAcl {
    */
   Table.KeyValue<String, OmKeyInfo> getPendingDeletionDir() throws IOException;
 
+  /**
+   * Returns an iterator for pending deleted directories.
+   * @throws IOException
+   */
+  TableIterator<String, ? extends Table.KeyValue<String, OmKeyInfo>> getDeletedDirEntries(
+      String volume, String bucket) throws IOException;
+
+  default List<Table.KeyValue<String, OmKeyInfo>> getDeletedDirEntries(String volume, String bucket, int size)
+      throws IOException {
+    List<Table.KeyValue<String, OmKeyInfo>> deletedDirEntries = new ArrayList<>(size);
+    try (TableIterator<String, ? extends  Table.KeyValue<String, OmKeyInfo>> iterator =
+             getDeletedDirEntries(volume, bucket)) {
+      while (deletedDirEntries.size() < size && iterator.hasNext()) {
+        Table.KeyValue<String, OmKeyInfo> kv = iterator.next();
+        deletedDirEntries.add(Table.newKeyValue(kv.getKey(), kv.getValue()));
+      }
+      return deletedDirEntries;
+    }
+  }
+
   /**
    * Returns all sub directories under the given parent directory.
    *
@@ -243,7 +289,7 @@ public interface KeyManager extends OzoneManagerFS, IOzoneAcl {
    * Returns the instance of Directory Deleting Service.
    * @return Background service.
    */
-  BackgroundService getDirDeletingService();
+  DirectoryDeletingService getDirDeletingService();
 
   /**
    * Returns the instance of Open Key Cleanup Service.
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManagerImpl.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManagerImpl.java
index 6d276d9528..e3f56f2dea 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManagerImpl.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManagerImpl.java
@@ -37,6 +37,7 @@ import java.util.Set;
 import java.util.Stack;
 import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -86,6 +87,7 @@ import org.apache.hadoop.ozone.om.helpers.OzoneAclUtil;
 import org.apache.hadoop.ozone.om.helpers.OzoneFSUtils;
 import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
+import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
 import org.apache.hadoop.ozone.om.request.OMClientRequest;
 import org.apache.hadoop.ozone.om.request.file.OMFileRequest;
 import org.apache.hadoop.ozone.om.request.util.OMMultipartUploadUtils;
@@ -189,7 +191,7 @@ public class KeyManagerImpl implements KeyManager {
 
   private final KeyProviderCryptoExtension kmsProvider;
   private final boolean enableFileSystemPaths;
-  private BackgroundService dirDeletingService;
+  private DirectoryDeletingService dirDeletingService;
   private final OMPerformanceMetrics metrics;
 
   private BackgroundService openKeyCleanupService;
@@ -305,7 +307,7 @@ public class KeyManagerImpl implements KeyManager {
       try {
         snapshotDeletingService = new SnapshotDeletingService(
             snapshotServiceInterval, snapshotServiceTimeout,
-            ozoneManager, scmClient.getBlockClient());
+            ozoneManager);
         snapshotDeletingService.start();
       } catch (IOException e) {
         LOG.error("Error starting Snapshot Deleting Service", e);
@@ -662,6 +664,60 @@ public class KeyManagerImpl implements KeyManager {
         .getPendingDeletionKeys(count, ozoneManager.getOmSnapshotManager());
   }
 
+  private <V, R> List<Table.KeyValue<String, R>> getTableEntries(String startKey,
+          TableIterator<String, ? extends Table.KeyValue<String, V>> tableIterator,
+          Function<V, R> valueFunction, int size) throws IOException {
+    List<Table.KeyValue<String, R>> entries = new ArrayList<>();
+    /* Seek to the start key if it not null. The next key in queue is ensured to start with the bucket
+         prefix, {@link org.apache.hadoop.hdds.utils.db.Table#iterator(bucketPrefix)} would ensure this.
+    */
+    if (startKey != null) {
+      tableIterator.seek(startKey);
+      tableIterator.seekToFirst();
+    }
+    int currentCount = 0;
+    while (tableIterator.hasNext() && currentCount < size) {
+      Table.KeyValue<String, V> kv = tableIterator.next();
+      if (kv != null) {
+        entries.add(Table.newKeyValue(kv.getKey(), valueFunction.apply(kv.getValue())));
+        currentCount++;
+      }
+    }
+    return entries;
+  }
+
+  private Optional<String> getBucketPrefix(String volumeName, String bucketName, boolean isFSO) throws IOException {
+    // Bucket prefix would be empty if both volume & bucket is empty i.e. either null or "".
+    if (StringUtils.isEmpty(volumeName) && StringUtils.isEmpty(bucketName)) {
+      return Optional.empty();
+    } else if (StringUtils.isEmpty(bucketName) || StringUtils.isEmpty(volumeName)) {
+      throw new IOException("One of volume : " + volumeName + ", bucket: " + bucketName + " is empty." +
+          " Either both should be empty or none of the arguments should be empty");
+    }
+    return isFSO ? Optional.of(metadataManager.getBucketKeyPrefixFSO(volumeName, bucketName)) :
+        Optional.of(metadataManager.getBucketKeyPrefix(volumeName, bucketName));
+  }
+
+  @Override
+  public List<Table.KeyValue<String, String>> getRenamesKeyEntries(
+      String volume, String bucket, String startKey, int size) throws IOException {
+    Optional<String> bucketPrefix = getBucketPrefix(volume, bucket, false);
+    try (TableIterator<String, ? extends Table.KeyValue<String, String>>
+             renamedKeyIter = metadataManager.getSnapshotRenamedTable().iterator(bucketPrefix.orElse(""))) {
+      return getTableEntries(startKey, renamedKeyIter, Function.identity(), size);
+    }
+  }
+
+  @Override
+  public List<Table.KeyValue<String, List<OmKeyInfo>>> getDeletedKeyEntries(
+      String volume, String bucket, String startKey, int size) throws IOException {
+    Optional<String> bucketPrefix = getBucketPrefix(volume, bucket, false);
+    try (TableIterator<String, ? extends Table.KeyValue<String, RepeatedOmKeyInfo>>
+             delKeyIter = metadataManager.getDeletedTable().iterator(bucketPrefix.orElse(""))) {
+      return getTableEntries(startKey, delKeyIter, RepeatedOmKeyInfo::cloneOmKeyInfoList, size);
+    }
+  }
+
   @Override
   public ExpiredOpenKeys getExpiredOpenKeys(Duration expireThreshold,
       int count, BucketLayout bucketLayout, Duration leaseThreshold) throws IOException {
@@ -688,7 +744,7 @@ public class KeyManagerImpl implements KeyManager {
   }
 
   @Override
-  public BackgroundService getDirDeletingService() {
+  public DirectoryDeletingService getDirDeletingService() {
     return dirDeletingService;
   }
 
@@ -723,8 +779,7 @@ public class KeyManagerImpl implements KeyManager {
             TimeUnit.MILLISECONDS);
     return serviceInterval != DISABLE_VALUE;
   }
-
-
+  
   @Override
   public OmMultipartUploadList listMultipartUploads(String volumeName,
       String bucketName, String prefix) throws OMException {
@@ -1325,7 +1380,6 @@ public class KeyManagerImpl implements KeyManager {
     return null;
   }
 
-
   private OzoneFileStatus getOzoneFileStatusFSO(OmKeyArgs args,
       String clientAddress, boolean skipFileNotFoundError) throws IOException {
     final String volumeName = args.getVolumeName();
@@ -1784,17 +1838,13 @@ public class KeyManagerImpl implements KeyManager {
       }
       fileStatusFinalList.add(fileStatus);
     }
-
     return sortPipelineInfo(fileStatusFinalList, keyInfoList,
         omKeyArgs, clientAddress);
   }
 
-
   private List<OzoneFileStatus> sortPipelineInfo(
       List<OzoneFileStatus> fileStatusFinalList, List<OmKeyInfo> keyInfoList,
       OmKeyArgs omKeyArgs, String clientAddress) throws IOException {
-
-
     if (omKeyArgs.getLatestVersionLocation()) {
       slimLocationVersion(keyInfoList.toArray(new OmKeyInfo[0]));
     }
@@ -1976,6 +2026,13 @@ public class KeyManagerImpl implements KeyManager {
     return null;
   }
 
+  @Override
+  public TableIterator<String, ? extends Table.KeyValue<String, OmKeyInfo>> getDeletedDirEntries(
+      String volume, String bucket) throws IOException {
+    Optional<String> bucketPrefix = getBucketPrefix(volume, bucket, true);
+    return metadataManager.getDeletedDirTable().iterator(bucketPrefix.orElse(""));
+  }
+
   @Override
   public List<OmKeyInfo> getPendingDeletionSubDirs(long volumeId, long bucketId,
       OmKeyInfo parentInfo, long numEntries) throws IOException {
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
index ee92dbc2fd..2e85fef162 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
@@ -824,7 +824,7 @@ public class OmMetadataManagerImpl implements OMMetadataManager,
   /**
    * Given a volume and bucket, return the corresponding DB key.
    *
-   * @param volume - User name
+   * @param volume - Volume name
    * @param bucket - Bucket name
    */
   @Override
@@ -838,6 +838,22 @@ public class OmMetadataManagerImpl implements OMMetadataManager,
     return builder.toString();
   }
 
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getBucketKeyPrefix(String volume, String bucket) {
+    return getOzoneKey(volume, bucket, OM_KEY_PREFIX);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getBucketKeyPrefixFSO(String volume, String bucket) throws IOException {
+    return getOzoneKeyFSO(volume, bucket, OM_KEY_PREFIX);
+  }
+
   @Override
   public String getOzoneKey(String volume, String bucket, String key) {
     StringBuilder builder = new StringBuilder()
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotChainManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotChainManager.java
index b069a174cd..e4102665d6 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotChainManager.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotChainManager.java
@@ -24,8 +24,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -56,6 +58,7 @@ public class SnapshotChainManager {
   private final ConcurrentMap<UUID, String> snapshotIdToTableKey;
   private UUID latestGlobalSnapshotId;
   private final boolean snapshotChainCorrupted;
+  private UUID oldestGlobalSnapshotId;
 
   public SnapshotChainManager(OMMetadataManager metadataManager) {
     globalSnapshotChain = Collections.synchronizedMap(new LinkedHashMap<>());
@@ -104,6 +107,8 @@ public class SnapshotChainManager {
       // On add snapshot, set previous snapshot entry nextSnapshotID =
       // snapshotID
       globalSnapshotChain.get(prevGlobalID).setNextSnapshotId(snapshotID);
+    } else {
+      oldestGlobalSnapshotId = snapshotID;
     }
 
     globalSnapshotChain.put(snapshotID,
@@ -171,7 +176,6 @@ public class SnapshotChainManager {
       // for node removal
       UUID next = globalSnapshotChain.get(snapshotID).getNextSnapshotId();
       UUID prev = globalSnapshotChain.get(snapshotID).getPreviousSnapshotId();
-
       if (prev != null && !globalSnapshotChain.containsKey(prev)) {
         throw new IOException(String.format(
             "Global snapshot chain corruption. " +
@@ -197,6 +201,9 @@ public class SnapshotChainManager {
       if (latestGlobalSnapshotId.equals(snapshotID)) {
         latestGlobalSnapshotId = prev;
       }
+      if (snapshotID.equals(oldestGlobalSnapshotId)) {
+        oldestGlobalSnapshotId = next;
+      }
       return true;
     } else {
       // snapshotID not found in snapshot chain, log warning and return
@@ -382,6 +389,42 @@ public class SnapshotChainManager {
     return latestGlobalSnapshotId;
   }
 
+  /**
+   * Get oldest of global snapshot in snapshot chain.
+   */
+  public UUID getOldestGlobalSnapshotId() throws IOException {
+    validateSnapshotChain();
+    return oldestGlobalSnapshotId;
+  }
+
+  public Iterator<UUID> iterator(final boolean reverse) throws IOException {
+    validateSnapshotChain();
+    return new Iterator<UUID>() {
+      private UUID currentSnapshotId = reverse ? getLatestGlobalSnapshotId() : getOldestGlobalSnapshotId();
+      @Override
+      public boolean hasNext() {
+        return currentSnapshotId != null;
+      }
+
+      @Override
+      public UUID next() {
+        try {
+          UUID prevSnapshotId = currentSnapshotId;
+          if (reverse && hasPreviousGlobalSnapshot(currentSnapshotId) ||
+              !reverse && hasNextGlobalSnapshot(currentSnapshotId)) {
+            currentSnapshotId =
+                reverse ? previousGlobalSnapshot(currentSnapshotId) : nextGlobalSnapshot(currentSnapshotId);
+          } else {
+            currentSnapshotId = null;
+          }
+          return prevSnapshotId;
+        } catch (IOException e) {
+          throw new UncheckedIOException("Error while getting next snapshot for " + currentSnapshotId, e);
+        }
+      }
+    };
+  }
+
   /**
    * Get latest path snapshot in snapshot chain.
    */
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
index ffaedaa06a..5e324b376f 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
@@ -78,6 +78,7 @@ import org.apache.hadoop.ozone.om.request.security.OMRenewDelegationTokenRequest
 import org.apache.hadoop.ozone.om.request.snapshot.OMSnapshotCreateRequest;
 import org.apache.hadoop.ozone.om.request.snapshot.OMSnapshotDeleteRequest;
 import org.apache.hadoop.ozone.om.request.snapshot.OMSnapshotMoveDeletedKeysRequest;
+import org.apache.hadoop.ozone.om.request.snapshot.OMSnapshotMoveTableKeysRequest;
 import org.apache.hadoop.ozone.om.request.snapshot.OMSnapshotPurgeRequest;
 import org.apache.hadoop.ozone.om.request.snapshot.OMSnapshotRenameRequest;
 import org.apache.hadoop.ozone.om.request.snapshot.OMSnapshotSetPropertyRequest;
@@ -232,6 +233,8 @@ public final class OzoneManagerRatisUtils {
       return new OMSnapshotRenameRequest(omRequest);
     case SnapshotMoveDeletedKeys:
       return new OMSnapshotMoveDeletedKeysRequest(omRequest);
+    case SnapshotMoveTableKeys:
+      return new OMSnapshotMoveTableKeysRequest(omRequest);
     case SnapshotPurge:
       return new OMSnapshotPurgeRequest(omRequest);
     case SetSnapshotProperty:
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotMoveDeletedKeysRequest.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotMoveDeletedKeysRequest.java
index 58fdb1232d..2ddf308bb5 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotMoveDeletedKeysRequest.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotMoveDeletedKeysRequest.java
@@ -82,7 +82,7 @@ public class OMSnapshotMoveDeletedKeysRequest extends OMClientRequest {
       // Check the snapshot exists.
       SnapshotUtils.getSnapshotInfo(ozoneManager, fromSnapshot.getTableKey());
 
-      nextSnapshot = SnapshotUtils.getNextActiveSnapshot(fromSnapshot, snapshotChainManager, ozoneManager);
+      nextSnapshot = SnapshotUtils.getNextSnapshot(ozoneManager, snapshotChainManager, fromSnapshot);
 
       // Get next non-deleted snapshot.
       List<SnapshotMoveKeyInfos> nextDBKeysList = moveDeletedKeysRequest.getNextDBKeysList();
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotMoveTableKeysRequest.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotMoveTableKeysRequest.java
new file mode 100644
index 0000000000..0eb0d3cd16
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotMoveTableKeysRequest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.om.request.snapshot;
+
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.TransactionInfo;
+import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
+import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
+import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.SnapshotChainManager;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
+import org.apache.hadoop.ozone.om.request.OMClientRequest;
+import org.apache.hadoop.ozone.om.request.util.OmResponseUtil;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.om.response.snapshot.OMSnapshotMoveTableKeysResponse;
+import org.apache.hadoop.ozone.om.snapshot.SnapshotUtils;
+import org.apache.hadoop.ozone.om.upgrade.DisallowedUntilLayoutVersion;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotMoveKeyInfos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotMoveTableKeysRequest;
+import org.apache.ratis.server.protocol.TermIndex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.apache.hadoop.hdds.HddsUtils.fromProtobuf;
+import static org.apache.hadoop.ozone.om.upgrade.OMLayoutFeature.FILESYSTEM_SNAPSHOT;
+
+/**
+ * Handles OMSnapshotMoveTableKeysRequest Request.
+ * This is an OM internal request. Does not need @RequireSnapshotFeatureState.
+ */
+public class OMSnapshotMoveTableKeysRequest extends OMClientRequest {
+
+  private static final Logger LOG = LoggerFactory.getLogger(OMSnapshotMoveTableKeysRequest.class);
+
+  public OMSnapshotMoveTableKeysRequest(OMRequest omRequest) {
+    super(omRequest);
+  }
+
+  @Override
+  public OMRequest preExecute(OzoneManager ozoneManager) throws IOException {
+    OmMetadataManagerImpl omMetadataManager = (OmMetadataManagerImpl) ozoneManager.getMetadataManager();
+    SnapshotChainManager snapshotChainManager = omMetadataManager.getSnapshotChainManager();
+    SnapshotMoveTableKeysRequest moveTableKeysRequest = getOmRequest().getSnapshotMoveTableKeysRequest();
+    SnapshotInfo fromSnapshot = SnapshotUtils.getSnapshotInfo(ozoneManager,
+        snapshotChainManager, fromProtobuf(moveTableKeysRequest.getFromSnapshotID()));
+    String bucketKeyPrefix = omMetadataManager.getBucketKeyPrefix(fromSnapshot.getVolumeName(),
+        fromSnapshot.getBucketName());
+    String bucketKeyPrefixFSO = omMetadataManager.getBucketKeyPrefixFSO(fromSnapshot.getVolumeName(),
+        fromSnapshot.getBucketName());
+
+    Set<String> keys = new HashSet<>();
+    List<SnapshotMoveKeyInfos> deletedKeys = new ArrayList<>(moveTableKeysRequest.getDeletedKeysList().size());
+
+    //validate deleted key starts with bucket prefix.[/<volName>/<bucketName>/]
+    for (SnapshotMoveKeyInfos deletedKey : moveTableKeysRequest.getDeletedKeysList()) {
+      // Filter only deleted keys with at least one keyInfo per key.
+      if (!deletedKey.getKeyInfosList().isEmpty()) {
+        deletedKeys.add(deletedKey);
+        if (!deletedKey.getKey().startsWith(bucketKeyPrefix)) {
+          throw new OMException("Deleted Key: " + deletedKey + " doesn't start with prefix " + bucketKeyPrefix,
+              OMException.ResultCodes.INVALID_KEY_NAME);
+        }
+        if (keys.contains(deletedKey.getKey())) {
+          throw new OMException("Duplicate Deleted Key: " + deletedKey + " in request",
+              OMException.ResultCodes.INVALID_REQUEST);
+        } else {
+          keys.add(deletedKey.getKey());
+        }
+      }
+    }
+
+    keys.clear();
+    List<HddsProtos.KeyValue> renamedKeysList = new ArrayList<>(moveTableKeysRequest.getRenamedKeysList().size());
+    //validate rename key starts with bucket prefix.[/<volName>/<bucketName>/]
+    for (HddsProtos.KeyValue renamedKey : moveTableKeysRequest.getRenamedKeysList()) {
+      if (renamedKey.hasKey() && renamedKey.hasValue()) {
+        renamedKeysList.add(renamedKey);
+        if (!renamedKey.getKey().startsWith(bucketKeyPrefix)) {
+          throw new OMException("Rename Key: " + renamedKey + " doesn't start with prefix " + bucketKeyPrefix,
+              OMException.ResultCodes.INVALID_KEY_NAME);
+        }
+        if (keys.contains(renamedKey.getKey())) {
+          throw new OMException("Duplicate rename Key: " + renamedKey + " in request",
+              OMException.ResultCodes.INVALID_REQUEST);
+        } else {
+          keys.add(renamedKey.getKey());
+        }
+      }
+    }
+    keys.clear();
+
+    // Filter only deleted dirs with only one keyInfo per key.
+    List<SnapshotMoveKeyInfos> deletedDirs = new ArrayList<>(moveTableKeysRequest.getDeletedDirsList().size());
+    //validate deleted key starts with bucket FSO path prefix.[/<volId>/<bucketId>/]
+    for (SnapshotMoveKeyInfos deletedDir : moveTableKeysRequest.getDeletedDirsList()) {
+      // Filter deleted directories with exactly one keyInfo per key.
+      if (deletedDir.getKeyInfosList().size() == 1) {
+        deletedDirs.add(deletedDir);
+        if (!deletedDir.getKey().startsWith(bucketKeyPrefixFSO)) {
+          throw new OMException("Deleted dir: " + deletedDir + " doesn't start with prefix " +
+              bucketKeyPrefixFSO, OMException.ResultCodes.INVALID_KEY_NAME);
+        }
+        if (keys.contains(deletedDir.getKey())) {
+          throw new OMException("Duplicate deleted dir Key: " + deletedDir + " in request",
+              OMException.ResultCodes.INVALID_REQUEST);
+        } else {
+          keys.add(deletedDir.getKey());
+        }
+      }
+    }
+    return getOmRequest().toBuilder().setSnapshotMoveTableKeysRequest(
+        moveTableKeysRequest.toBuilder().clearDeletedDirs().clearDeletedKeys().clearRenamedKeys()
+            .addAllDeletedKeys(deletedKeys).addAllDeletedDirs(deletedDirs)
+            .addAllRenamedKeys(renamedKeysList).build()).build();
+  }
+
+  @Override
+  @DisallowedUntilLayoutVersion(FILESYSTEM_SNAPSHOT)
+  public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, TermIndex termIndex) {
+    OmMetadataManagerImpl omMetadataManager = (OmMetadataManagerImpl) ozoneManager.getMetadataManager();
+    SnapshotChainManager snapshotChainManager = omMetadataManager.getSnapshotChainManager();
+
+    SnapshotMoveTableKeysRequest moveTableKeysRequest = getOmRequest().getSnapshotMoveTableKeysRequest();
+
+    OMClientResponse omClientResponse;
+    OzoneManagerProtocolProtos.OMResponse.Builder omResponse = OmResponseUtil.getOMResponseBuilder(getOmRequest());
+    try {
+      SnapshotInfo fromSnapshot = SnapshotUtils.getSnapshotInfo(ozoneManager,
+          snapshotChainManager, fromProtobuf(moveTableKeysRequest.getFromSnapshotID()));
+      // If there is no snapshot in the chain after the current snapshot move the keys to Active Object Store.
+      SnapshotInfo nextSnapshot = SnapshotUtils.getNextSnapshot(ozoneManager, snapshotChainManager, fromSnapshot);
+
+      // If next snapshot is not active then ignore move. Since this could be a redundant operations.
+      if (nextSnapshot != null && nextSnapshot.getSnapshotStatus() != SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE) {
+        throw new OMException("Next snapshot : " + nextSnapshot + " in chain is not active.",
+            OMException.ResultCodes.INVALID_SNAPSHOT_ERROR);
+      }
+
+      // Update lastTransactionInfo for fromSnapshot and the nextSnapshot.
+      fromSnapshot.setLastTransactionInfo(TransactionInfo.valueOf(termIndex).toByteString());
+      omMetadataManager.getSnapshotInfoTable().addCacheEntry(new CacheKey<>(fromSnapshot.getTableKey()),
+          CacheValue.get(termIndex.getIndex(), fromSnapshot));
+      if (nextSnapshot != null) {
+        nextSnapshot.setLastTransactionInfo(TransactionInfo.valueOf(termIndex).toByteString());
+        omMetadataManager.getSnapshotInfoTable().addCacheEntry(new CacheKey<>(nextSnapshot.getTableKey()),
+            CacheValue.get(termIndex.getIndex(), nextSnapshot));
+      }
+      omClientResponse = new OMSnapshotMoveTableKeysResponse(omResponse.build(), fromSnapshot, nextSnapshot,
+          moveTableKeysRequest.getDeletedKeysList(), moveTableKeysRequest.getDeletedDirsList(),
+          moveTableKeysRequest.getRenamedKeysList());
+    } catch (IOException ex) {
+      omClientResponse = new OMSnapshotMoveTableKeysResponse(createErrorOMResponse(omResponse, ex));
+    }
+    return omClientResponse;
+  }
+}
+
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotPurgeRequest.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotPurgeRequest.java
index 6602f52514..ca29d4e112 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotPurgeRequest.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotPurgeRequest.java
@@ -105,7 +105,7 @@ public class OMSnapshotPurgeRequest extends OMClientRequest {
         }
 
         SnapshotInfo nextSnapshot =
-            SnapshotUtils.getNextActiveSnapshot(fromSnapshot, snapshotChainManager, ozoneManager);
+            SnapshotUtils.getNextSnapshot(ozoneManager, snapshotChainManager, fromSnapshot);
 
         // Step 1: Update the deep clean flag for the next active snapshot
         updateSnapshotInfoAndCache(nextSnapshot, omMetadataManager, trxnLogIndex);
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotMoveDeletedKeysResponse.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotMoveDeletedKeysResponse.java
index f39d5827a0..7d1b7f237b 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotMoveDeletedKeysResponse.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotMoveDeletedKeysResponse.java
@@ -40,6 +40,7 @@ import java.io.IOException;
 import java.util.List;
 
 import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.SNAPSHOT_INFO_TABLE;
+import static org.apache.hadoop.ozone.om.snapshot.SnapshotUtils.createMergedRepeatedOmKeyInfoFromDeletedTableEntry;
 
 /**
  * Response for OMSnapshotMoveDeletedKeysRequest.
@@ -199,8 +200,7 @@ public class OMSnapshotMoveDeletedKeysResponse extends OMClientResponse {
     }
 
     for (SnapshotMoveKeyInfos dBKey : nextDBKeysList) {
-      RepeatedOmKeyInfo omKeyInfos =
-          createRepeatedOmKeyInfo(dBKey, metadataManager);
+      RepeatedOmKeyInfo omKeyInfos = createMergedRepeatedOmKeyInfoFromDeletedTableEntry(dBKey, metadataManager);
       if (omKeyInfos == null) {
         continue;
       }
@@ -223,36 +223,5 @@ public class OMSnapshotMoveDeletedKeysResponse extends OMClientResponse {
 
     return result;
   }
-
-  private RepeatedOmKeyInfo createRepeatedOmKeyInfo(
-      SnapshotMoveKeyInfos snapshotMoveKeyInfos,
-      OMMetadataManager metadataManager) throws IOException {
-    String dbKey = snapshotMoveKeyInfos.getKey();
-    List<KeyInfo> keyInfoList = snapshotMoveKeyInfos.getKeyInfosList();
-    // When older version of keys are moved to the next snapshot's deletedTable
-    // The newer version might also be in the next snapshot's deletedTable and
-    // it might overwrite. This is to avoid that and also avoid having
-    // orphans blocks.
-    RepeatedOmKeyInfo result = metadataManager.getDeletedTable().get(dbKey);
-
-    for (KeyInfo keyInfo : keyInfoList) {
-      OmKeyInfo omKeyInfo = OmKeyInfo.getFromProtobuf(keyInfo);
-      if (result == null) {
-        result = new RepeatedOmKeyInfo(omKeyInfo);
-      } else if (!isSameAsLatestOmKeyInfo(omKeyInfo, result)) {
-        result.addOmKeyInfo(omKeyInfo);
-      }
-    }
-
-    return result;
-  }
-
-  private boolean isSameAsLatestOmKeyInfo(OmKeyInfo omKeyInfo,
-                                          RepeatedOmKeyInfo result) {
-    int size = result.getOmKeyInfoList().size();
-    assert size > 0;
-    OmKeyInfo keyInfoFromRepeated = result.getOmKeyInfoList().get(size - 1);
-    return omKeyInfo.equals(keyInfoFromRepeated);
-  }
 }
 
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotMoveTableKeysResponse.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotMoveTableKeysResponse.java
new file mode 100644
index 0000000000..b06570afb1
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotMoveTableKeysResponse.java
@@ -0,0 +1,162 @@
+/*
+ * 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.om.response.snapshot;
+
+import jakarta.annotation.Nonnull;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.BatchOperation;
+import org.apache.hadoop.hdds.utils.db.RDBStore;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
+import org.apache.hadoop.ozone.om.OmSnapshot;
+import org.apache.hadoop.ozone.om.OmSnapshotManager;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
+import org.apache.hadoop.ozone.om.response.CleanupTableInfo;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotMoveKeyInfos;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.apache.hadoop.ozone.om.OmMetadataManagerImpl.SNAPSHOT_INFO_TABLE;
+import static org.apache.hadoop.ozone.om.snapshot.SnapshotUtils.createMergedRepeatedOmKeyInfoFromDeletedTableEntry;
+
+/**
+ * Response for OMSnapshotMoveDeletedKeysRequest.
+ */
+@CleanupTableInfo(cleanupTables = {SNAPSHOT_INFO_TABLE})
+public class OMSnapshotMoveTableKeysResponse extends OMClientResponse {
+
+  private SnapshotInfo fromSnapshot;
+  private SnapshotInfo nextSnapshot;
+  private List<SnapshotMoveKeyInfos> deletedKeys;
+  private List<HddsProtos.KeyValue> renameKeysList;
+  private List<SnapshotMoveKeyInfos> deletedDirs;
+
+  public OMSnapshotMoveTableKeysResponse(OMResponse omResponse,
+                                         @Nonnull SnapshotInfo fromSnapshot, SnapshotInfo nextSnapshot,
+                                         List<SnapshotMoveKeyInfos> deletedKeys,
+                                         List<SnapshotMoveKeyInfos> deletedDirs,
+                                         List<HddsProtos.KeyValue> renamedKeys) {
+    super(omResponse);
+    this.fromSnapshot = fromSnapshot;
+    this.nextSnapshot = nextSnapshot;
+    this.deletedKeys = deletedKeys;
+    this.renameKeysList = renamedKeys;
+    this.deletedDirs = deletedDirs;
+  }
+
+  /**
+   * For when the request is not successful.
+   * For a successful request, the other constructor should be used.
+   */
+  public OMSnapshotMoveTableKeysResponse(@Nonnull OMResponse omResponse) {
+    super(omResponse);
+    checkStatusNotOK();
+  }
+
+  @Override
+  protected void addToDBBatch(OMMetadataManager omMetadataManager, BatchOperation batchOperation) throws IOException {
+    OmSnapshotManager omSnapshotManager = ((OmMetadataManagerImpl) omMetadataManager)
+        .getOzoneManager().getOmSnapshotManager();
+
+    try (ReferenceCounted<OmSnapshot> rcOmFromSnapshot =
+             omSnapshotManager.getSnapshot(fromSnapshot.getSnapshotId())) {
+
+      OmSnapshot fromOmSnapshot = rcOmFromSnapshot.get();
+
+      if (nextSnapshot != null) {
+        try (ReferenceCounted<OmSnapshot>
+            rcOmNextSnapshot = omSnapshotManager.getSnapshot(nextSnapshot.getSnapshotId())) {
+
+          OmSnapshot nextOmSnapshot = rcOmNextSnapshot.get();
+          RDBStore nextSnapshotStore = (RDBStore) nextOmSnapshot.getMetadataManager().getStore();
+          // Init Batch Operation for snapshot db.
+          try (BatchOperation writeBatch = nextSnapshotStore.initBatchOperation()) {
+            addKeysToNextSnapshot(writeBatch, nextOmSnapshot.getMetadataManager());
+            nextSnapshotStore.commitBatchOperation(writeBatch);
+            nextSnapshotStore.getDb().flushWal(true);
+            nextSnapshotStore.getDb().flush();
+          }
+        }
+      } else {
+        // Handle the case where there is no next Snapshot.
+        addKeysToNextSnapshot(batchOperation, omMetadataManager);
+      }
+
+      // Update From Snapshot Deleted Table.
+      RDBStore fromSnapshotStore = (RDBStore) fromOmSnapshot.getMetadataManager().getStore();
+      try (BatchOperation fromSnapshotBatchOp = fromSnapshotStore.initBatchOperation()) {
+        deleteKeysFromSnapshot(fromSnapshotBatchOp, fromOmSnapshot.getMetadataManager());
+        fromSnapshotStore.commitBatchOperation(fromSnapshotBatchOp);
+        fromSnapshotStore.getDb().flushWal(true);
+        fromSnapshotStore.getDb().flush();
+      }
+    }
+
+    // Flush snapshot info to rocksDB.
+    omMetadataManager.getSnapshotInfoTable().putWithBatch(batchOperation, fromSnapshot.getTableKey(), fromSnapshot);
+    if (nextSnapshot != null) {
+      omMetadataManager.getSnapshotInfoTable().putWithBatch(batchOperation, nextSnapshot.getTableKey(), nextSnapshot);
+    }
+  }
+
+  private void deleteKeysFromSnapshot(BatchOperation batchOp, OMMetadataManager fromSnapshotMetadataManager)
+      throws IOException {
+    for (SnapshotMoveKeyInfos deletedOmKeyInfo : deletedKeys) {
+      // Delete keys from current snapshot that are moved to next snapshot.
+      fromSnapshotMetadataManager.getDeletedTable().deleteWithBatch(batchOp, deletedOmKeyInfo.getKey());
+    }
+
+    // Delete rename keys from current snapshot that are moved to next snapshot.
+    for (HddsProtos.KeyValue renameEntry : renameKeysList) {
+      fromSnapshotMetadataManager.getSnapshotRenamedTable().deleteWithBatch(batchOp, renameEntry.getKey());
+    }
+
+    // Delete deletedDir from current snapshot that are moved to next snapshot.
+    for (SnapshotMoveKeyInfos deletedDirInfo : deletedDirs) {
+      fromSnapshotMetadataManager.getDeletedDirTable().deleteWithBatch(batchOp, deletedDirInfo.getKey());
+    }
+
+  }
+
+  private void addKeysToNextSnapshot(BatchOperation batchOp, OMMetadataManager metadataManager) throws IOException {
+
+    // Add renamed keys to the next snapshot or active DB.
+    for (HddsProtos.KeyValue renameEntry : renameKeysList) {
+      metadataManager.getSnapshotRenamedTable().putWithBatch(batchOp, renameEntry.getKey(), renameEntry.getValue());
+    }
+    // Add deleted keys to the next snapshot or active DB.
+    for (SnapshotMoveKeyInfos deletedKeyInfo : deletedKeys) {
+      RepeatedOmKeyInfo omKeyInfos = createMergedRepeatedOmKeyInfoFromDeletedTableEntry(deletedKeyInfo,
+          metadataManager);
+      metadataManager.getDeletedTable().putWithBatch(batchOp, deletedKeyInfo.getKey(), omKeyInfos);
+    }
+    // Add deleted dir keys to the next snapshot or active DB.
+    for (SnapshotMoveKeyInfos deletedDirInfo : deletedDirs) {
+      metadataManager.getDeletedDirTable().putWithBatch(batchOp, deletedDirInfo.getKey(),
+          OmKeyInfo.getFromProtobuf(deletedDirInfo.getKeyInfosList().get(0)));
+    }
+  }
+}
+
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/DirectoryDeletingService.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/DirectoryDeletingService.java
index c8703c3c4c..ad16c49d5e 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/DirectoryDeletingService.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/DirectoryDeletingService.java
@@ -82,6 +82,7 @@ public class DirectoryDeletingService extends AbstractKeyDeletingService {
   private final long pathLimitPerTask;
   private final int ratisByteLimit;
   private final AtomicBoolean suspended;
+  private AtomicBoolean isRunningOnAOS;
 
   public DirectoryDeletingService(long interval, TimeUnit unit,
       long serviceTimeout, OzoneManager ozoneManager,
@@ -98,6 +99,7 @@ public class DirectoryDeletingService extends AbstractKeyDeletingService {
     // always go to 90% of max limit for request as other header will be added
     this.ratisByteLimit = (int) (limit * 0.9);
     this.suspended = new AtomicBoolean(false);
+    this.isRunningOnAOS = new AtomicBoolean(false);
   }
 
   private boolean shouldRun() {
@@ -108,6 +110,10 @@ public class DirectoryDeletingService extends AbstractKeyDeletingService {
     return getOzoneManager().isLeaderReady() && !suspended.get();
   }
 
+  public boolean isRunningOnAOS() {
+    return isRunningOnAOS.get();
+  }
+
   /**
    * Suspend the service.
    */
@@ -127,11 +133,16 @@ public class DirectoryDeletingService extends AbstractKeyDeletingService {
   @Override
   public BackgroundTaskQueue getTasks() {
     BackgroundTaskQueue queue = new BackgroundTaskQueue();
-    queue.add(new DirectoryDeletingService.DirDeletingTask());
+    queue.add(new DirectoryDeletingService.DirDeletingTask(this));
     return queue;
   }
 
-  private class DirDeletingTask implements BackgroundTask {
+  private final class DirDeletingTask implements BackgroundTask {
+    private final DirectoryDeletingService directoryDeletingService;
+
+    private DirDeletingTask(DirectoryDeletingService service) {
+      this.directoryDeletingService = service;
+    }
 
     @Override
     public int getPriority() {
@@ -144,6 +155,7 @@ public class DirectoryDeletingService extends AbstractKeyDeletingService {
         if (LOG.isDebugEnabled()) {
           LOG.debug("Running DirectoryDeletingService");
         }
+        isRunningOnAOS.set(true);
         getRunCount().incrementAndGet();
         long dirNum = 0L;
         long subDirNum = 0L;
@@ -210,8 +222,11 @@ public class DirectoryDeletingService extends AbstractKeyDeletingService {
           LOG.error("Error while running delete directories and files " +
               "background task. Will retry at next run.", e);
         }
+        isRunningOnAOS.set(false);
+        synchronized (directoryDeletingService) {
+          this.directoryDeletingService.notify();
+        }
       }
-
       // place holder by returning empty results of this call back.
       return BackgroundTaskResult.EmptyTaskResult.newResult();
     }
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyDeletingService.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyDeletingService.java
index 5e622cb170..e7553004ed 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyDeletingService.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyDeletingService.java
@@ -92,6 +92,7 @@ public class KeyDeletingService extends AbstractKeyDeletingService {
   private final Map<String, Long> exclusiveReplicatedSizeMap;
   private final Set<String> completedExclusiveSizeSet;
   private final Map<String, String> snapshotSeekMap;
+  private AtomicBoolean isRunningOnAOS;
 
   public KeyDeletingService(OzoneManager ozoneManager,
       ScmBlockLocationProtocol scmClient,
@@ -111,6 +112,7 @@ public class KeyDeletingService extends AbstractKeyDeletingService {
     this.exclusiveReplicatedSizeMap = new HashMap<>();
     this.completedExclusiveSizeSet = new HashSet<>();
     this.snapshotSeekMap = new HashMap<>();
+    this.isRunningOnAOS = new AtomicBoolean(false);
   }
 
   /**
@@ -123,10 +125,14 @@ public class KeyDeletingService extends AbstractKeyDeletingService {
     return deletedKeyCount;
   }
 
+  public boolean isRunningOnAOS() {
+    return isRunningOnAOS.get();
+  }
+
   @Override
   public BackgroundTaskQueue getTasks() {
     BackgroundTaskQueue queue = new BackgroundTaskQueue();
-    queue.add(new KeyDeletingTask());
+    queue.add(new KeyDeletingTask(this));
     return queue;
   }
 
@@ -169,7 +175,12 @@ public class KeyDeletingService extends AbstractKeyDeletingService {
    * the blocks info in its deletedBlockLog), it removes these keys from the
    * DB.
    */
-  private class KeyDeletingTask implements BackgroundTask {
+  private final class KeyDeletingTask implements BackgroundTask {
+    private final KeyDeletingService deletingService;
+
+    private KeyDeletingTask(KeyDeletingService service) {
+      this.deletingService = service;
+    }
 
     @Override
     public int getPriority() {
@@ -183,7 +194,7 @@ public class KeyDeletingService extends AbstractKeyDeletingService {
       if (shouldRun()) {
         final long run = getRunCount().incrementAndGet();
         LOG.debug("Running KeyDeletingService {}", run);
-
+        isRunningOnAOS.set(true);
         int delCount = 0;
         try {
           // TODO: [SNAPSHOT] HDDS-7968. Reclaim eligible key blocks in
@@ -217,6 +228,11 @@ public class KeyDeletingService extends AbstractKeyDeletingService {
         }
 
       }
+      isRunningOnAOS.set(false);
+      synchronized (deletingService) {
+        this.deletingService.notify();
+      }
+
       // By design, no one cares about the results of this call back.
       return EmptyTaskResult.newResult();
     }
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDeletingService.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDeletingService.java
index f85bd781b0..edc6c7a162 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDeletingService.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/SnapshotDeletingService.java
@@ -20,54 +20,49 @@ package org.apache.hadoop.ozone.om.service;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.protobuf.ServiceException;
-import org.apache.commons.lang3.tuple.Pair;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.conf.StorageUnit;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
-import org.apache.hadoop.hdds.scm.protocol.ScmBlockLocationProtocol;
 import org.apache.hadoop.hdds.utils.BackgroundTask;
 import org.apache.hadoop.hdds.utils.BackgroundTaskQueue;
 import org.apache.hadoop.hdds.utils.BackgroundTaskResult;
 import org.apache.hadoop.hdds.utils.db.Table;
-import org.apache.hadoop.hdds.utils.db.TableIterator;
 import org.apache.hadoop.ozone.ClientVersion;
-import org.apache.hadoop.ozone.OzoneConsts;
-import org.apache.hadoop.ozone.common.BlockGroup;
 import org.apache.hadoop.ozone.lock.BootstrapStateHandler;
+import org.apache.hadoop.ozone.om.KeyManager;
 import org.apache.hadoop.ozone.om.OMConfigKeys;
 import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
 import org.apache.hadoop.ozone.om.OmSnapshot;
 import org.apache.hadoop.ozone.om.OmSnapshotManager;
 import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.SnapshotChainManager;
-import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
-import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo;
 import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
 import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup;
-import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
 import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerRatisUtils;
 import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted;
+import org.apache.hadoop.ozone.om.snapshot.SnapshotUtils;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
-import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PurgePathRequest;
-import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotMoveDeletedKeysRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotMoveKeyInfos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotMoveTableKeysRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotPurgeRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type;
-import org.apache.hadoop.util.Time;
 import org.apache.ratis.protocol.ClientId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
+import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
 
-import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX;
+import static org.apache.hadoop.hdds.HddsUtils.toProtobuf;
 import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_KEY_DELETING_LIMIT_PER_TASK;
 import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_KEY_DELETING_LIMIT_PER_TASK_DEFAULT;
 import static org.apache.hadoop.ozone.om.OMConfigKeys.SNAPSHOT_DELETING_LIMIT_PER_TASK;
@@ -93,16 +88,17 @@ public class SnapshotDeletingService extends AbstractKeyDeletingService {
   private final AtomicBoolean suspended;
   private final OzoneConfiguration conf;
   private final AtomicLong successRunCount;
-  private final long snapshotDeletionPerTask;
-  private final int keyLimitPerSnapshot;
+  private final int keyLimitPerTask;
+  private final int snapshotDeletionPerTask;
   private final int ratisByteLimit;
+  private final long serviceTimeout;
 
   public SnapshotDeletingService(long interval, long serviceTimeout,
-      OzoneManager ozoneManager, ScmBlockLocationProtocol scmClient)
+                                 OzoneManager ozoneManager)
       throws IOException {
     super(SnapshotDeletingService.class.getSimpleName(), interval,
         TimeUnit.MILLISECONDS, SNAPSHOT_DELETING_CORE_POOL_SIZE,
-        serviceTimeout, ozoneManager, scmClient);
+        serviceTimeout, ozoneManager, null);
     this.ozoneManager = ozoneManager;
     this.omSnapshotManager = ozoneManager.getOmSnapshotManager();
     OmMetadataManagerImpl omMetadataManager = (OmMetadataManagerImpl)
@@ -111,8 +107,7 @@ public class SnapshotDeletingService extends AbstractKeyDeletingService {
     this.successRunCount = new AtomicLong(0);
     this.suspended = new AtomicBoolean(false);
     this.conf = ozoneManager.getConfiguration();
-    this.snapshotDeletionPerTask = conf
-        .getLong(SNAPSHOT_DELETING_LIMIT_PER_TASK,
+    this.snapshotDeletionPerTask = conf.getInt(SNAPSHOT_DELETING_LIMIT_PER_TASK,
         SNAPSHOT_DELETING_LIMIT_PER_TASK_DEFAULT);
     int limit = (int) conf.getStorageSize(
         OMConfigKeys.OZONE_OM_RATIS_LOG_APPENDER_QUEUE_BYTE_LIMIT,
@@ -120,9 +115,35 @@ public class SnapshotDeletingService extends AbstractKeyDeletingService {
         StorageUnit.BYTES);
     // always go to 90% of max limit for request as other header will be added
     this.ratisByteLimit = (int) (limit * 0.9);
-    this.keyLimitPerSnapshot = conf.getInt(
+    this.keyLimitPerTask = conf.getInt(
         OZONE_SNAPSHOT_KEY_DELETING_LIMIT_PER_TASK,
         OZONE_SNAPSHOT_KEY_DELETING_LIMIT_PER_TASK_DEFAULT);
+    this.serviceTimeout = serviceTimeout;
+  }
+
+  // Wait for a notification from KeyDeletingService if the key deletion is running. This is to ensure, merging of
+  // entries do not start while the AOS is still processing the deleted keys.
+  @VisibleForTesting
+  public void waitForKeyDeletingService() throws InterruptedException {
+    KeyDeletingService keyDeletingService = getOzoneManager().getKeyManager().getDeletingService();
+    synchronized (keyDeletingService) {
+      while (keyDeletingService.isRunningOnAOS()) {
+        keyDeletingService.wait(serviceTimeout);
+      }
+    }
+  }
+
+  // Wait for a notification from DirectoryDeletingService if the directory deletion is running. This is to ensure,
+  // merging of entries do not start while the AOS is still processing the deleted keys.
+  @VisibleForTesting
+  public void waitForDirDeletingService() throws InterruptedException {
+    DirectoryDeletingService directoryDeletingService = getOzoneManager().getKeyManager()
+        .getDirDeletingService();
+    synchronized (directoryDeletingService) {
+      while (directoryDeletingService.isRunningOnAOS()) {
+        directoryDeletingService.wait(serviceTimeout);
+      }
+    }
   }
 
   private class SnapshotDeletingTask implements BackgroundTask {
@@ -136,316 +157,89 @@ public class SnapshotDeletingService extends AbstractKeyDeletingService {
 
       getRunCount().incrementAndGet();
 
-      ReferenceCounted<OmSnapshot> rcOmSnapshot = null;
-      ReferenceCounted<OmSnapshot> rcOmPreviousSnapshot = null;
-
-      Table<String, SnapshotInfo> snapshotInfoTable =
-          ozoneManager.getMetadataManager().getSnapshotInfoTable();
-      List<String> purgeSnapshotKeys = new ArrayList<>();
-      try (TableIterator<String, ? extends Table.KeyValue
-          <String, SnapshotInfo>> iterator = snapshotInfoTable.iterator()) {
-
+      try {
+        int remaining = keyLimitPerTask;
+        Iterator<UUID> iterator = chainManager.iterator(true);
+        List<String> snapshotsToBePurged = new ArrayList<>();
         long snapshotLimit = snapshotDeletionPerTask;
-
-        while (iterator.hasNext() && snapshotLimit > 0) {
-          SnapshotInfo snapInfo = iterator.next().getValue();
-
-          // Only Iterate in deleted snapshot
+        while (iterator.hasNext() && snapshotLimit > 0 && remaining > 0) {
+          SnapshotInfo snapInfo = SnapshotUtils.getSnapshotInfo(ozoneManager, chainManager, iterator.next());
           if (shouldIgnoreSnapshot(snapInfo)) {
             continue;
           }
-
-          // Note: Can refactor this to use try-with-resources.
-          // Handling RC decrements manually for now to minimize conflicts.
-          rcOmSnapshot = omSnapshotManager.getSnapshot(
-              snapInfo.getVolumeName(),
-              snapInfo.getBucketName(),
-              snapInfo.getName());
-          OmSnapshot omSnapshot = rcOmSnapshot.get();
-
-          Table<String, RepeatedOmKeyInfo> snapshotDeletedTable =
-              omSnapshot.getMetadataManager().getDeletedTable();
-          Table<String, OmKeyInfo> snapshotDeletedDirTable =
-              omSnapshot.getMetadataManager().getDeletedDirTable();
-
-          Table<String, String> renamedTable =
-              omSnapshot.getMetadataManager().getSnapshotRenamedTable();
-
-          long volumeId = ozoneManager.getMetadataManager()
-              .getVolumeId(snapInfo.getVolumeName());
-          // Get bucketInfo for the snapshot bucket to get bucket layout.
-          String dbBucketKey = ozoneManager.getMetadataManager().getBucketKey(
-              snapInfo.getVolumeName(), snapInfo.getBucketName());
-          OmBucketInfo bucketInfo = ozoneManager.getMetadataManager()
-              .getBucketTable().get(dbBucketKey);
-
-          if (bucketInfo == null) {
-            // Decrement ref count
-            rcOmSnapshot.close();
-            rcOmSnapshot = null;
-            throw new IllegalStateException("Bucket " + "/" +
-                snapInfo.getVolumeName() + "/" + snapInfo.getBucketName() +
-                " is not found. BucketInfo should not be null for snapshotted" +
-                " bucket. The OM is in unexpected state.");
-          }
-
-          String snapshotBucketKey = dbBucketKey + OzoneConsts.OM_KEY_PREFIX;
-          String dbBucketKeyForDir = ozoneManager.getMetadataManager()
-              .getBucketKey(Long.toString(volumeId),
-                  Long.toString(bucketInfo.getObjectID())) + OM_KEY_PREFIX;
-
-          if (isSnapshotReclaimable(snapshotDeletedTable,
-              snapshotDeletedDirTable, snapshotBucketKey, dbBucketKeyForDir)) {
-            purgeSnapshotKeys.add(snapInfo.getTableKey());
-            // Decrement ref count
-            rcOmSnapshot.close();
-            rcOmSnapshot = null;
+          LOG.info("Started Snapshot Deletion Processing for snapshot : {}", snapInfo.getTableKey());
+          SnapshotInfo nextSnapshot = SnapshotUtils.getNextSnapshot(ozoneManager, chainManager, snapInfo);
+          // Continue if the next snapshot is not active. This is to avoid unnecessary copies from one snapshot to
+          // another.
+          if (nextSnapshot != null &&
+              nextSnapshot.getSnapshotStatus() != SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE) {
             continue;
           }
 
-          //TODO: [SNAPSHOT] Add lock to deletedTable and Active DB.
-          SnapshotInfo previousSnapshot = getPreviousActiveSnapshot(snapInfo, chainManager);
-          Table<String, OmKeyInfo> previousKeyTable = null;
-          Table<String, OmDirectoryInfo> previousDirTable = null;
-          OmSnapshot omPreviousSnapshot = null;
-
-          // Split RepeatedOmKeyInfo and update current snapshot deletedKeyTable
-          // and next snapshot deletedKeyTable.
-          if (previousSnapshot != null) {
-            rcOmPreviousSnapshot = omSnapshotManager.getSnapshot(
-                previousSnapshot.getVolumeName(),
-                previousSnapshot.getBucketName(),
-                previousSnapshot.getName());
-            omPreviousSnapshot = rcOmPreviousSnapshot.get();
-
-            previousKeyTable = omPreviousSnapshot
-                .getMetadataManager().getKeyTable(bucketInfo.getBucketLayout());
-            previousDirTable = omPreviousSnapshot
-                .getMetadataManager().getDirectoryTable();
+          // nextSnapshot = null means entries would be moved to AOS.
+          if (nextSnapshot == null) {
+            waitForKeyDeletingService();
+            waitForDirDeletingService();
           }
-
-          // Move key to either next non deleted snapshot's deletedTable
-          // or keep it in current snapshot deleted table.
-          List<SnapshotMoveKeyInfos> toReclaimList = new ArrayList<>();
-          List<SnapshotMoveKeyInfos> toNextDBList = new ArrayList<>();
-          // A list of renamed keys/files/dirs
-          List<HddsProtos.KeyValue> renamedList = new ArrayList<>();
-          List<String> dirsToMove = new ArrayList<>();
-
-          long remainNum = handleDirectoryCleanUp(snapshotDeletedDirTable,
-              previousDirTable, renamedTable, dbBucketKeyForDir, snapInfo,
-              omSnapshot, dirsToMove, renamedList);
-          int deletionCount = 0;
-
-          try (TableIterator<String, ? extends Table.KeyValue<String,
-              RepeatedOmKeyInfo>> deletedIterator = snapshotDeletedTable
-              .iterator()) {
-
-            List<BlockGroup> keysToPurge = new ArrayList<>();
-            deletedIterator.seek(snapshotBucketKey);
-
-            while (deletedIterator.hasNext() &&
-                deletionCount < remainNum) {
-              Table.KeyValue<String, RepeatedOmKeyInfo>
-                  deletedKeyValue = deletedIterator.next();
-              String deletedKey = deletedKeyValue.getKey();
-
-              // Exit if it is out of the bucket scope.
-              if (!deletedKey.startsWith(snapshotBucketKey)) {
-                // If snapshot deletedKeyTable doesn't have any
-                // entry in the snapshot scope it can be reclaimed
-                break;
-              }
-
-              RepeatedOmKeyInfo repeatedOmKeyInfo = deletedKeyValue.getValue();
-
-              SnapshotMoveKeyInfos.Builder toReclaim = SnapshotMoveKeyInfos
-                  .newBuilder()
-                  .setKey(deletedKey);
-              SnapshotMoveKeyInfos.Builder toNextDb = SnapshotMoveKeyInfos
-                  .newBuilder()
-                  .setKey(deletedKey);
-              HddsProtos.KeyValue.Builder renamedKey = HddsProtos.KeyValue
-                  .newBuilder();
-
-              for (OmKeyInfo keyInfo : repeatedOmKeyInfo.getOmKeyInfoList()) {
-                splitRepeatedOmKeyInfo(toReclaim, toNextDb, renamedKey,
-                    keyInfo, previousKeyTable, renamedTable,
-                    bucketInfo, volumeId);
+          try (ReferenceCounted<OmSnapshot> snapshot = omSnapshotManager.getSnapshot(
+              snapInfo.getVolumeName(), snapInfo.getBucketName(), snapInfo.getName())) {
+            KeyManager snapshotKeyManager = snapshot.get().getKeyManager();
+            int moveCount = 0;
+            // Get all entries from deletedKeyTable.
+            List<Table.KeyValue<String, List<OmKeyInfo>>> deletedKeyEntries =
+                snapshotKeyManager.getDeletedKeyEntries(snapInfo.getVolumeName(), snapInfo.getBucketName(),
+                    null, remaining);
+            moveCount += deletedKeyEntries.size();
+            // Get all entries from deletedDirTable.
+            List<Table.KeyValue<String, OmKeyInfo>> deletedDirEntries = snapshotKeyManager.getDeletedDirEntries(
+                snapInfo.getVolumeName(), snapInfo.getBucketName(), remaining - moveCount);
+            moveCount += deletedDirEntries.size();
+            // Get all entries from snapshotRenamedTable.
+            List<Table.KeyValue<String, String>> renameEntries = snapshotKeyManager.getRenamesKeyEntries(
+                snapInfo.getVolumeName(), snapInfo.getBucketName(), null, remaining - moveCount);
+            moveCount += renameEntries.size();
+            if (moveCount > 0) {
+              List<SnapshotMoveKeyInfos> deletedKeys = new ArrayList<>(deletedKeyEntries.size());
+              List<SnapshotMoveKeyInfos> deletedDirs = new ArrayList<>(deletedDirEntries.size());
+              List<HddsProtos.KeyValue> renameKeys = new ArrayList<>(renameEntries.size());
+
+              // Convert deletedKeyEntries to SnapshotMoveKeyInfos.
+              for (Table.KeyValue<String, List<OmKeyInfo>> deletedEntry : deletedKeyEntries) {
+                deletedKeys.add(SnapshotMoveKeyInfos.newBuilder().setKey(deletedEntry.getKey())
+                    .addAllKeyInfos(deletedEntry.getValue()
+                        .stream().map(val -> val.getProtobuf(ClientVersion.CURRENT_VERSION))
+                        .collect(Collectors.toList())).build());
               }
 
-              // If all the KeyInfos are reclaimable in RepeatedOmKeyInfo
-              // then no need to update current snapshot deletedKeyTable.
-              if (!(toReclaim.getKeyInfosCount() ==
-                  repeatedOmKeyInfo.getOmKeyInfoList().size())) {
-                toReclaimList.add(toReclaim.build());
-                toNextDBList.add(toNextDb.build());
-              } else {
-                // The key can be reclaimed here.
-                List<BlockGroup> blocksForKeyDelete = omSnapshot
-                    .getMetadataManager()
-                    .getBlocksForKeyDelete(deletedKey);
-                if (blocksForKeyDelete != null) {
-                  keysToPurge.addAll(blocksForKeyDelete);
-                }
+              // Convert deletedDirEntries to SnapshotMoveKeyInfos.
+              for (Table.KeyValue<String, OmKeyInfo> deletedDirEntry : deletedDirEntries) {
+                deletedDirs.add(SnapshotMoveKeyInfos.newBuilder().setKey(deletedDirEntry.getKey())
+                    .addKeyInfos(deletedDirEntry.getValue().getProtobuf(ClientVersion.CURRENT_VERSION)).build());
               }
 
-              if (renamedKey.hasKey() && renamedKey.hasValue()) {
-                renamedList.add(renamedKey.build());
+              // Convert renamedEntries to KeyValue.
+              for (Table.KeyValue<String, String> renameEntry : renameEntries) {
+                renameKeys.add(HddsProtos.KeyValue.newBuilder().setKey(renameEntry.getKey())
+                    .setValue(renameEntry.getValue()).build());
               }
-              deletionCount++;
+              submitSnapshotMoveDeletedKeys(snapInfo, deletedKeys, renameKeys, deletedDirs);
+              remaining -= moveCount;
+            } else {
+              snapshotsToBePurged.add(snapInfo.getTableKey());
             }
-
-            // Delete keys From deletedTable
-            processKeyDeletes(keysToPurge, omSnapshot.getKeyManager(),
-                null, snapInfo.getTableKey());
-            successRunCount.incrementAndGet();
-          } catch (IOException ex) {
-            LOG.error("Error while running Snapshot Deleting Service for " +
-                "snapshot " + snapInfo.getTableKey() + " with snapshotId " +
-                snapInfo.getSnapshotId() + ". Processed " + deletionCount +
-                " keys and " + (keyLimitPerSnapshot - remainNum) +
-                " directories and files", ex);
           }
+          successRunCount.incrementAndGet();
           snapshotLimit--;
-          // Submit Move request to OM.
-          submitSnapshotMoveDeletedKeys(snapInfo, toReclaimList,
-              toNextDBList, renamedList, dirsToMove);
-
-          // Properly decrement ref count for rcOmPreviousSnapshot
-          if (rcOmPreviousSnapshot != null) {
-            rcOmPreviousSnapshot.close();
-            rcOmPreviousSnapshot = null;
-          }
+        }
+        if (!snapshotsToBePurged.isEmpty()) {
+          submitSnapshotPurgeRequest(snapshotsToBePurged);
         }
       } catch (IOException e) {
         LOG.error("Error while running Snapshot Deleting Service", e);
-      } finally {
-        // Decrement ref counts
-        if (rcOmPreviousSnapshot != null) {
-          rcOmPreviousSnapshot.close();
-        }
-        if (rcOmSnapshot != null) {
-          rcOmSnapshot.close();
-        }
       }
-      submitSnapshotPurgeRequest(purgeSnapshotKeys);
-
       return BackgroundTaskResult.EmptyTaskResult.newResult();
     }
 
-    private boolean isSnapshotReclaimable(
-        Table<String, RepeatedOmKeyInfo> snapshotDeletedTable,
-        Table<String, OmKeyInfo> snapshotDeletedDirTable,
-        String snapshotBucketKey, String dbBucketKeyForDir) throws IOException {
-
-      boolean isDirTableCleanedUp = false;
-      boolean isKeyTableCleanedUp  = false;
-      try (TableIterator<String, ? extends Table.KeyValue<String,
-          RepeatedOmKeyInfo>> iterator = snapshotDeletedTable.iterator();) {
-        iterator.seek(snapshotBucketKey);
-        // If the next entry doesn't start with snapshotBucketKey then
-        // deletedKeyTable is already cleaned up.
-        isKeyTableCleanedUp = !iterator.hasNext() || !iterator.next().getKey()
-            .startsWith(snapshotBucketKey);
-      }
-
-      try (TableIterator<String, ? extends Table.KeyValue<String, OmKeyInfo>>
-               iterator = snapshotDeletedDirTable.iterator()) {
-        iterator.seek(dbBucketKeyForDir);
-        // If the next entry doesn't start with dbBucketKeyForDir then
-        // deletedDirTable is already cleaned up.
-        isDirTableCleanedUp = !iterator.hasNext() || !iterator.next().getKey()
-            .startsWith(dbBucketKeyForDir);
-      }
-
-      return (isDirTableCleanedUp || snapshotDeletedDirTable.isEmpty()) &&
-          (isKeyTableCleanedUp || snapshotDeletedTable.isEmpty());
-    }
-
-    @SuppressWarnings("checkstyle:ParameterNumber")
-    private long handleDirectoryCleanUp(
-        Table<String, OmKeyInfo> snapshotDeletedDirTable,
-        Table<String, OmDirectoryInfo> previousDirTable,
-        Table<String, String> renamedTable,
-        String dbBucketKeyForDir, SnapshotInfo snapInfo,
-        OmSnapshot omSnapshot, List<String> dirsToMove,
-        List<HddsProtos.KeyValue> renamedList) {
-
-      long dirNum = 0L;
-      long subDirNum = 0L;
-      long subFileNum = 0L;
-      long remainNum = keyLimitPerSnapshot;
-      int consumedSize = 0;
-      List<PurgePathRequest> purgePathRequestList = new ArrayList<>();
-      List<Pair<String, OmKeyInfo>> allSubDirList
-          = new ArrayList<>(keyLimitPerSnapshot);
-      try (TableIterator<String, ? extends
-          Table.KeyValue<String, OmKeyInfo>> deletedDirIterator =
-               snapshotDeletedDirTable.iterator()) {
-
-        long startTime = Time.monotonicNow();
-        deletedDirIterator.seek(dbBucketKeyForDir);
-
-        while (deletedDirIterator.hasNext()) {
-          Table.KeyValue<String, OmKeyInfo> deletedDir =
-              deletedDirIterator.next();
-          String deletedDirKey = deletedDir.getKey();
-
-          // Exit for dirs out of snapshot scope.
-          if (!deletedDirKey.startsWith(dbBucketKeyForDir)) {
-            break;
-          }
-
-          if (isDirReclaimable(deletedDir, previousDirTable,
-              renamedTable, renamedList)) {
-            // Reclaim here
-            PurgePathRequest request = prepareDeleteDirRequest(
-                remainNum, deletedDir.getValue(), deletedDir.getKey(),
-                allSubDirList, omSnapshot.getKeyManager());
-            if (isBufferLimitCrossed(ratisByteLimit, consumedSize,
-                request.getSerializedSize())) {
-              if (purgePathRequestList.size() != 0) {
-                // if message buffer reaches max limit, avoid sending further
-                remainNum = 0;
-                break;
-              }
-              // if directory itself is having a lot of keys / files,
-              // reduce capacity to minimum level
-              remainNum = MIN_ERR_LIMIT_PER_TASK;
-              request = prepareDeleteDirRequest(
-                  remainNum, deletedDir.getValue(), deletedDir.getKey(),
-                  allSubDirList, omSnapshot.getKeyManager());
-            }
-            consumedSize += request.getSerializedSize();
-            purgePathRequestList.add(request);
-            remainNum = remainNum - request.getDeletedSubFilesCount();
-            remainNum = remainNum - request.getMarkDeletedSubDirsCount();
-            // Count up the purgeDeletedDir, subDirs and subFiles
-            if (request.getDeletedDir() != null
-                && !request.getDeletedDir().isEmpty()) {
-              dirNum++;
-            }
-            subDirNum += request.getMarkDeletedSubDirsCount();
-            subFileNum += request.getDeletedSubFilesCount();
-          } else {
-            dirsToMove.add(deletedDir.getKey());
-          }
-        }
-
-        remainNum = optimizeDirDeletesAndSubmitRequest(remainNum, dirNum,
-            subDirNum, subFileNum, allSubDirList, purgePathRequestList,
-            snapInfo.getTableKey(), startTime, ratisByteLimit - consumedSize,
-            omSnapshot.getKeyManager());
-      } catch (IOException e) {
-        LOG.error("Error while running delete directories and files for " +
-            "snapshot " + snapInfo.getTableKey() + " in snapshot deleting " +
-            "background task. Will retry at next run.", e);
-      }
-
-      return remainNum;
-    }
-
     private void submitSnapshotPurgeRequest(List<String> purgeSnapshotKeys) {
       if (!purgeSnapshotKeys.isEmpty()) {
         SnapshotPurgeRequest snapshotPurgeRequest = SnapshotPurgeRequest
@@ -463,92 +257,36 @@ public class SnapshotDeletingService extends AbstractKeyDeletingService {
       }
     }
 
-    @SuppressWarnings("checkstyle:ParameterNumber")
-    private void splitRepeatedOmKeyInfo(SnapshotMoveKeyInfos.Builder toReclaim,
-        SnapshotMoveKeyInfos.Builder toNextDb,
-        HddsProtos.KeyValue.Builder renamedKey, OmKeyInfo keyInfo,
-        Table<String, OmKeyInfo> previousKeyTable,
-        Table<String, String> renamedTable,
-        OmBucketInfo bucketInfo, long volumeId) throws IOException {
-
-      if (isKeyReclaimable(previousKeyTable, renamedTable,
-          keyInfo, bucketInfo, volumeId, renamedKey)) {
-        // Update in current db's deletedKeyTable
-        toReclaim.addKeyInfos(keyInfo
-            .getProtobuf(ClientVersion.CURRENT_VERSION));
-      } else {
-        // Move to next non deleted snapshot's deleted table
-        toNextDb.addKeyInfos(keyInfo.getProtobuf(
-            ClientVersion.CURRENT_VERSION));
-      }
-    }
-
-    private boolean isDirReclaimable(
-        Table.KeyValue<String, OmKeyInfo> deletedDir,
-        Table<String, OmDirectoryInfo> previousDirTable,
-        Table<String, String> renamedTable,
-        List<HddsProtos.KeyValue> renamedList) throws IOException {
-
-      if (previousDirTable == null) {
-        return true;
-      }
-
-      String deletedDirDbKey = deletedDir.getKey();
-      OmKeyInfo deletedDirInfo = deletedDir.getValue();
-      String dbRenameKey = ozoneManager.getMetadataManager().getRenameKey(
-          deletedDirInfo.getVolumeName(), deletedDirInfo.getBucketName(),
-          deletedDirInfo.getObjectID());
-
-      /*
-      snapshotRenamedTable: /volumeName/bucketName/objectID ->
-          /volumeId/bucketId/parentId/dirName
-       */
-      String dbKeyBeforeRename = renamedTable.getIfExist(dbRenameKey);
-      String prevDbKey = null;
-
-      if (dbKeyBeforeRename != null) {
-        prevDbKey = dbKeyBeforeRename;
-        HddsProtos.KeyValue renamedDir = HddsProtos.KeyValue
-            .newBuilder()
-            .setKey(dbRenameKey)
-            .setValue(dbKeyBeforeRename)
-            .build();
-        renamedList.add(renamedDir);
-      } else {
-        // In OMKeyDeleteResponseWithFSO OzonePathKey is converted to
-        // OzoneDeletePathKey. Changing it back to check the previous DirTable.
-        prevDbKey = ozoneManager.getMetadataManager()
-            .getOzoneDeletePathDirKey(deletedDirDbKey);
-      }
-
-      OmDirectoryInfo prevDirectoryInfo = previousDirTable.get(prevDbKey);
-      if (prevDirectoryInfo == null) {
-        return true;
-      }
-
-      return prevDirectoryInfo.getObjectID() != deletedDirInfo.getObjectID();
-    }
-
-    public void submitSnapshotMoveDeletedKeys(SnapshotInfo snapInfo,
-        List<SnapshotMoveKeyInfos> toReclaimList,
-        List<SnapshotMoveKeyInfos> toNextDBList,
-        List<HddsProtos.KeyValue> renamedList,
-        List<String> dirsToMove) throws InterruptedException {
+    private void submitSnapshotMoveDeletedKeys(SnapshotInfo snapInfo,
+                                               List<SnapshotMoveKeyInfos> deletedKeys,
+                                               List<HddsProtos.KeyValue> renamedList,
+                                               List<SnapshotMoveKeyInfos> dirsToMove) {
 
-      SnapshotMoveDeletedKeysRequest.Builder moveDeletedKeysBuilder =
-          SnapshotMoveDeletedKeysRequest.newBuilder()
-              .setFromSnapshot(snapInfo.getProtobuf());
+      SnapshotMoveTableKeysRequest.Builder moveDeletedKeysBuilder = SnapshotMoveTableKeysRequest.newBuilder()
+          .setFromSnapshotID(toProtobuf(snapInfo.getSnapshotId()));
 
-      SnapshotMoveDeletedKeysRequest moveDeletedKeys = moveDeletedKeysBuilder
-          .addAllReclaimKeys(toReclaimList)
-          .addAllNextDBKeys(toNextDBList)
+      SnapshotMoveTableKeysRequest moveDeletedKeys = moveDeletedKeysBuilder
+          .addAllDeletedKeys(deletedKeys)
           .addAllRenamedKeys(renamedList)
-          .addAllDeletedDirsToMove(dirsToMove)
+          .addAllDeletedDirs(dirsToMove)
           .build();
+      if (isBufferLimitCrossed(ratisByteLimit, 0, moveDeletedKeys.getSerializedSize())) {
+        int remaining = MIN_ERR_LIMIT_PER_TASK;
+        deletedKeys = deletedKeys.subList(0, Math.min(remaining, deletedKeys.size()));
+        remaining -= deletedKeys.size();
+        renamedList = renamedList.subList(0, Math.min(remaining, renamedList.size()));
+        remaining -= renamedList.size();
+        dirsToMove = dirsToMove.subList(0, Math.min(remaining, dirsToMove.size()));
+        moveDeletedKeys = moveDeletedKeysBuilder
+            .addAllDeletedKeys(deletedKeys)
+            .addAllRenamedKeys(renamedList)
+            .addAllDeletedDirs(dirsToMove)
+            .build();
+      }
 
       OMRequest omRequest = OMRequest.newBuilder()
-          .setCmdType(Type.SnapshotMoveDeletedKeys)
-          .setSnapshotMoveDeletedKeysRequest(moveDeletedKeys)
+          .setCmdType(Type.SnapshotMoveTableKeys)
+          .setSnapshotMoveTableKeysRequest(moveDeletedKeys)
           .setClientId(clientId.toString())
           .build();
 
@@ -557,20 +295,26 @@ public class SnapshotDeletingService extends AbstractKeyDeletingService {
       }
     }
 
-    public void submitRequest(OMRequest omRequest) {
+    private void submitRequest(OMRequest omRequest) {
       try {
         OzoneManagerRatisUtils.submitRequest(ozoneManager, omRequest, clientId, getRunCount().get());
       } catch (ServiceException e) {
-        LOG.error("Snapshot Deleting request failed. " +
-            "Will retry at next run.", e);
+        LOG.error("Request: {} fired by SnapshotDeletingService failed. Will retry in the next run", omRequest, e);
       }
     }
   }
 
+  /**
+   * Checks if a given snapshot has been deleted and all the changes made to snapshot have been flushed to disk.
+   * @param snapInfo SnapshotInfo corresponding to the snapshot.
+   * @return true if the snapshot is still active or changes to snapshot have not been flushed to disk otherwise false.
+   * @throws IOException
+   */
   @VisibleForTesting
-  boolean shouldIgnoreSnapshot(SnapshotInfo snapInfo) {
+  boolean shouldIgnoreSnapshot(SnapshotInfo snapInfo) throws IOException {
     SnapshotInfo.SnapshotStatus snapshotStatus = snapInfo.getSnapshotStatus();
-    return snapshotStatus != SnapshotInfo.SnapshotStatus.SNAPSHOT_DELETED;
+    return snapshotStatus != SnapshotInfo.SnapshotStatus.SNAPSHOT_DELETED ||
+        !OmSnapshotManager.areSnapshotChangesFlushedToDB(getOzoneManager().getMetadataManager(), snapInfo);
   }
 
   // TODO: Move this util class.
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotUtils.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotUtils.java
index e0f40dabd8..7af6d08513 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotUtils.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/SnapshotUtils.java
@@ -24,8 +24,11 @@ import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
 import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.SnapshotChainManager;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo.SnapshotStatus;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
 import org.rocksdb.ColumnFamilyHandle;
 import org.rocksdb.RocksDBException;
 import org.slf4j.Logger;
@@ -33,6 +36,8 @@ import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.HashMap;
 import java.util.Map;
@@ -86,6 +91,13 @@ public final class SnapshotUtils {
     return snapshotInfo;
   }
 
+  public static SnapshotInfo getSnapshotInfo(OzoneManager ozoneManager,
+                                             SnapshotChainManager chainManager,
+                                             UUID snapshotId) throws IOException {
+    String tableKey = chainManager.getTableKey(snapshotId);
+    return SnapshotUtils.getSnapshotInfo(ozoneManager, tableKey);
+  }
+
   public static void dropColumnFamilyHandle(
       final ManagedRocksDB rocksDB,
       final ColumnFamilyHandle columnFamilyHandle) {
@@ -138,37 +150,25 @@ public final class SnapshotUtils {
     }
   }
 
+
   /**
-   * Get the next non deleted snapshot in the snapshot chain.
+   * Get the next snapshot in the snapshot chain.
    */
-  public static SnapshotInfo getNextActiveSnapshot(SnapshotInfo snapInfo,
-      SnapshotChainManager chainManager, OzoneManager ozoneManager)
+  public static SnapshotInfo getNextSnapshot(OzoneManager ozoneManager,
+                                             SnapshotChainManager chainManager,
+                                             SnapshotInfo snapInfo)
       throws IOException {
-
     // If the snapshot is deleted in the previous run, then the in-memory
     // SnapshotChainManager might throw NoSuchElementException as the snapshot
     // is removed in-memory but OMDoubleBuffer has not flushed yet.
     if (snapInfo == null) {
       throw new OMException("Snapshot Info is null. Cannot get the next snapshot", INVALID_SNAPSHOT_ERROR);
     }
-
     try {
-      while (chainManager.hasNextPathSnapshot(snapInfo.getSnapshotPath(),
+      if (chainManager.hasNextPathSnapshot(snapInfo.getSnapshotPath(),
           snapInfo.getSnapshotId())) {
-
-        UUID nextPathSnapshot =
-            chainManager.nextPathSnapshot(
-                snapInfo.getSnapshotPath(), snapInfo.getSnapshotId());
-
-        String tableKey = chainManager.getTableKey(nextPathSnapshot);
-        SnapshotInfo nextSnapshotInfo = getSnapshotInfo(ozoneManager, tableKey);
-
-        if (nextSnapshotInfo.getSnapshotStatus().equals(
-            SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE)) {
-          return nextSnapshotInfo;
-        }
-
-        snapInfo = nextSnapshotInfo;
+        UUID nextPathSnapshot = chainManager.nextPathSnapshot(snapInfo.getSnapshotPath(), snapInfo.getSnapshotId());
+        return getSnapshotInfo(ozoneManager, chainManager, nextPathSnapshot);
       }
     } catch (NoSuchElementException ex) {
       LOG.error("The snapshot {} is not longer in snapshot chain, It " +
@@ -242,4 +242,44 @@ public final class SnapshotUtils {
     final long bucketId = metadataManager.getBucketId(volumeName, bucketName);
     return OM_KEY_PREFIX + volumeId + OM_KEY_PREFIX + bucketId + OM_KEY_PREFIX;
   }
+
+  /**
+   * Returns merged repeatedKeyInfo entry with the existing deleted entry in the table.
+   * @param snapshotMoveKeyInfos keyInfos to be added.
+   * @param metadataManager metadataManager for a store.
+   * @return
+   * @throws IOException
+   */
+  public static RepeatedOmKeyInfo createMergedRepeatedOmKeyInfoFromDeletedTableEntry(
+      OzoneManagerProtocolProtos.SnapshotMoveKeyInfos snapshotMoveKeyInfos, OMMetadataManager metadataManager) throws
+      IOException {
+    String dbKey = snapshotMoveKeyInfos.getKey();
+    List<OmKeyInfo> keyInfoList = new ArrayList<>();
+    for (OzoneManagerProtocolProtos.KeyInfo info : snapshotMoveKeyInfos.getKeyInfosList()) {
+      OmKeyInfo fromProtobuf = OmKeyInfo.getFromProtobuf(info);
+      keyInfoList.add(fromProtobuf);
+    }
+    // When older version of keys are moved to the next snapshot's deletedTable
+    // The newer version might also be in the next snapshot's deletedTable and
+    // it might overwrite the existing value which inturn could lead to orphan block in the system.
+    // Checking the keyInfoList with the last n versions of the omKeyInfo versions would ensure all versions are
+    // present in the list and would also avoid redundant additions to the list if the last n versions match, which
+    // can happen on om transaction replay on snapshotted rocksdb.
+    RepeatedOmKeyInfo result = metadataManager.getDeletedTable().get(dbKey);
+    if (result == null) {
+      result = new RepeatedOmKeyInfo(keyInfoList);
+    } else if (!isSameAsLatestOmKeyInfo(keyInfoList, result)) {
+      keyInfoList.forEach(result::addOmKeyInfo);
+    }
+    return result;
+  }
+
+  private static boolean isSameAsLatestOmKeyInfo(List<OmKeyInfo> omKeyInfos,
+                                                 RepeatedOmKeyInfo result) {
+    int size = result.getOmKeyInfoList().size();
+    if (size >= omKeyInfos.size()) {
+      return omKeyInfos.equals(result.getOmKeyInfoList().subList(size - omKeyInfos.size(), size));
+    }
+    return false;
+  }
 }
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/OMRequestTestUtils.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/OMRequestTestUtils.java
index c807c04688..eff23a18e6 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/OMRequestTestUtils.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/OMRequestTestUtils.java
@@ -30,9 +30,11 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 import javax.xml.bind.DatatypeConverter;
 import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hdds.HddsUtils;
 import org.apache.hadoop.hdds.client.BlockID;
@@ -40,6 +42,7 @@ import org.apache.hadoop.hdds.client.ReplicationConfig;
 import org.apache.hadoop.hdds.client.ReplicationConfigValidator;
 import org.apache.hadoop.hdds.conf.ConfigurationSource;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.KeyValue;
 import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
 import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
 import org.apache.hadoop.ozone.ClientVersion;
@@ -109,6 +112,7 @@ import org.apache.hadoop.util.StringUtils;
 import org.apache.hadoop.util.Time;
 import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
+import org.apache.logging.log4j.util.Strings;
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doCallRealMethod;
@@ -749,17 +753,17 @@ public final class OMRequestTestUtils {
         .setClientId(UUID.randomUUID().toString());
   }
 
-  public static List< HddsProtos.KeyValue> getMetadataList() {
-    List<HddsProtos.KeyValue> metadataList = new ArrayList<>();
-    metadataList.add(HddsProtos.KeyValue.newBuilder().setKey("key1").setValue(
+  public static List< KeyValue> getMetadataList() {
+    List<KeyValue> metadataList = new ArrayList<>();
+    metadataList.add(KeyValue.newBuilder().setKey("key1").setValue(
         "value1").build());
-    metadataList.add(HddsProtos.KeyValue.newBuilder().setKey("key2").setValue(
+    metadataList.add(KeyValue.newBuilder().setKey("key2").setValue(
         "value2").build());
     return metadataList;
   }
 
-  public static HddsProtos.KeyValue fsoMetadata() {
-    return HddsProtos.KeyValue.newBuilder()
+  public static KeyValue fsoMetadata() {
+    return KeyValue.newBuilder()
         .setKey(OMConfigKeys.OZONE_OM_ENABLE_FILESYSTEM_PATHS)
         .setValue(Boolean.FALSE.toString())
         .build();
@@ -1050,7 +1054,7 @@ public final class OMRequestTestUtils {
         .setMultipartNumber(partNumber)
         .setMultipartUploadID(multipartUploadID)
         .addAllKeyLocations(new ArrayList<>())
-        .addMetadata(HddsProtos.KeyValue.newBuilder()
+        .addMetadata(KeyValue.newBuilder()
             .setKey(OzoneConsts.ETAG)
             .setValue(DatatypeConverter.printHexBinary(
                 new DigestInputStream(
@@ -1321,6 +1325,69 @@ public final class OMRequestTestUtils {
         .build();
   }
 
+  public static OMRequest moveSnapshotTableKeyRequest(UUID snapshotId,
+                                                      List<Pair<String, List<OmKeyInfo>>> deletedKeys,
+                                                      List<Pair<String, List<OmKeyInfo>>> deletedDirs,
+                                                      List<Pair<String, String>> renameKeys) {
+    List<OzoneManagerProtocolProtos.SnapshotMoveKeyInfos> deletedMoveKeys = new ArrayList<>();
+    for (Pair<String, List<OmKeyInfo>> deletedKey : deletedKeys) {
+      OzoneManagerProtocolProtos.SnapshotMoveKeyInfos snapshotMoveKeyInfos =
+          OzoneManagerProtocolProtos.SnapshotMoveKeyInfos.newBuilder()
+              .setKey(deletedKey.getKey())
+              .addAllKeyInfos(
+                  deletedKey.getValue().stream()
+                  .map(omKeyInfo -> omKeyInfo.getProtobuf(ClientVersion.CURRENT_VERSION)).collect(Collectors.toList()))
+              .build();
+      deletedMoveKeys.add(snapshotMoveKeyInfos);
+    }
+
+    List<OzoneManagerProtocolProtos.SnapshotMoveKeyInfos> deletedDirMoveKeys = new ArrayList<>();
+    for (Pair<String, List<OmKeyInfo>> deletedKey : deletedDirs) {
+      OzoneManagerProtocolProtos.SnapshotMoveKeyInfos snapshotMoveKeyInfos =
+          OzoneManagerProtocolProtos.SnapshotMoveKeyInfos.newBuilder()
+              .setKey(deletedKey.getKey())
+              .addAllKeyInfos(
+                  deletedKey.getValue().stream()
+                      .map(omKeyInfo -> omKeyInfo.getProtobuf(ClientVersion.CURRENT_VERSION))
+                      .collect(Collectors.toList()))
+              .build();
+      deletedDirMoveKeys.add(snapshotMoveKeyInfos);
+    }
+
+    List<KeyValue> renameKeyList = new ArrayList<>();
+    for (Pair<String, String> renameKey : renameKeys) {
+      KeyValue.Builder keyValue = KeyValue.newBuilder();
+      keyValue.setKey(renameKey.getKey());
+      if (!Strings.isBlank(renameKey.getValue())) {
+        keyValue.setValue(renameKey.getValue());
+      }
+      renameKeyList.add(keyValue.build());
+    }
+
+
+    OzoneManagerProtocolProtos.SnapshotMoveTableKeysRequest snapshotMoveTableKeysRequest =
+        OzoneManagerProtocolProtos.SnapshotMoveTableKeysRequest.newBuilder()
+            .setFromSnapshotID(HddsUtils.toProtobuf(snapshotId))
+            .addAllDeletedKeys(deletedMoveKeys)
+            .addAllDeletedDirs(deletedDirMoveKeys)
+            .addAllRenamedKeys(renameKeyList)
+            .build();
+
+    OzoneManagerProtocolProtos.UserInfo userInfo =
+        OzoneManagerProtocolProtos.UserInfo.newBuilder()
+            .setUserName("user")
+            .setHostName("host")
+            .setRemoteAddress("remote-address")
+            .build();
+
+    return OMRequest.newBuilder()
+        .setSnapshotMoveTableKeysRequest(snapshotMoveTableKeysRequest)
+        .setCmdType(Type.SnapshotMoveTableKeys)
+        .setClientId(UUID.randomUUID().toString())
+        .setUserInfo(userInfo)
+        .build();
+  }
+
   /**
    * Create OMRequest for Rename Snapshot.
    *
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotCreateRequest.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotCreateRequest.java
index 806c1b90f7..af90438225 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotCreateRequest.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotCreateRequest.java
@@ -19,15 +19,8 @@
 package org.apache.hadoop.ozone.om.request.snapshot;
 
 import org.apache.hadoop.hdds.client.RatisReplicationConfig;
-import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.utils.TransactionInfo;
-import org.apache.hadoop.hdds.utils.db.BatchOperation;
 import org.apache.hadoop.hdds.utils.db.Table;
-import org.apache.hadoop.ozone.audit.AuditLogger;
-import org.apache.hadoop.ozone.audit.AuditMessage;
-import org.apache.hadoop.ozone.om.OMConfigKeys;
-import org.apache.hadoop.ozone.om.OMMetrics;
-import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
 import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
@@ -38,18 +31,15 @@ import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
 import org.apache.hadoop.ozone.om.response.OMClientResponse;
 import org.apache.hadoop.ozone.om.response.key.OMKeyRenameResponse;
 import org.apache.hadoop.ozone.om.response.key.OMKeyRenameResponseWithFSO;
-import org.apache.hadoop.ozone.om.upgrade.OMLayoutVersionManager;
+import org.apache.hadoop.ozone.om.snapshot.TestSnapshotRequestAndResponse;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
-import java.io.File;
 import java.io.IOException;
 import java.util.UUID;
 
@@ -65,69 +55,19 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.framework;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 /**
  * Tests OMSnapshotCreateRequest class, which handles CreateSnapshot request.
  */
-public class TestOMSnapshotCreateRequest {
-  @TempDir
-  private File anotherTempDir;
-
-  private OzoneManager ozoneManager;
-  private OMMetrics omMetrics;
-  private OmMetadataManagerImpl omMetadataManager;
-  private BatchOperation batchOperation;
-
-  private String volumeName;
-  private String bucketName;
+public class TestOMSnapshotCreateRequest extends TestSnapshotRequestAndResponse {
   private String snapshotName1;
   private String snapshotName2;
 
   @BeforeEach
   public void setup() throws Exception {
-    ozoneManager = mock(OzoneManager.class);
-    omMetrics = OMMetrics.create();
-    OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
-    ozoneConfiguration.set(OMConfigKeys.OZONE_OM_DB_DIRS,
-        anotherTempDir.getAbsolutePath());
-    omMetadataManager = new OmMetadataManagerImpl(ozoneConfiguration,
-        ozoneManager);
-    when(ozoneManager.getMetrics()).thenReturn(omMetrics);
-    when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
-    when(ozoneManager.isRatisEnabled()).thenReturn(true);
-    when(ozoneManager.isFilesystemSnapshotEnabled()).thenReturn(true);
-    when(ozoneManager.isAdmin(any())).thenReturn(false);
-    when(ozoneManager.isOwner(any(), any())).thenReturn(false);
-    when(ozoneManager.getBucketOwner(any(), any(),
-        any(), any())).thenReturn("dummyBucketOwner");
-    OMLayoutVersionManager lvm = mock(OMLayoutVersionManager.class);
-    when(lvm.isAllowed(anyString())).thenReturn(true);
-    when(ozoneManager.getVersionManager()).thenReturn(lvm);
-    AuditLogger auditLogger = mock(AuditLogger.class);
-    when(ozoneManager.getAuditLogger()).thenReturn(auditLogger);
-    doNothing().when(auditLogger).logWrite(any(AuditMessage.class));
-    batchOperation = omMetadataManager.getStore().initBatchOperation();
-
-    volumeName = UUID.randomUUID().toString();
-    bucketName = UUID.randomUUID().toString();
     snapshotName1 = UUID.randomUUID().toString();
     snapshotName2 = UUID.randomUUID().toString();
-    OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
-        omMetadataManager);
-  }
-
-  @AfterEach
-  public void stop() {
-    omMetrics.unRegister();
-    framework().clearInlineMocks();
-    if (batchOperation != null) {
-      batchOperation.close();
-    }
   }
 
   @ValueSource(strings = {
@@ -140,9 +80,9 @@ public class TestOMSnapshotCreateRequest {
   })
   @ParameterizedTest
   public void testPreExecute(String snapshotName) throws Exception {
-    when(ozoneManager.isOwner(any(), any())).thenReturn(true);
-    OMRequest omRequest = createSnapshotRequest(volumeName,
-        bucketName, snapshotName);
+    when(getOzoneManager().isOwner(any(), any())).thenReturn(true);
+    OMRequest omRequest = createSnapshotRequest(getVolumeName(),
+        getBucketName(), snapshotName);
     doPreExecute(omRequest);
   }
 
@@ -158,9 +98,9 @@ public class TestOMSnapshotCreateRequest {
   })
   @ParameterizedTest
   public void testPreExecuteFailure(String snapshotName) {
-    when(ozoneManager.isOwner(any(), any())).thenReturn(true);
-    OMRequest omRequest = createSnapshotRequest(volumeName,
-        bucketName, snapshotName);
+    when(getOzoneManager().isOwner(any(), any())).thenReturn(true);
+    OMRequest omRequest = createSnapshotRequest(getVolumeName(),
+        getBucketName(), snapshotName);
     OMException omException =
         assertThrows(OMException.class, () -> doPreExecute(omRequest));
     assertTrue(omException.getMessage()
@@ -170,8 +110,8 @@ public class TestOMSnapshotCreateRequest {
   @Test
   public void testPreExecuteBadOwner() {
     // Owner is not set for the request.
-    OMRequest omRequest = createSnapshotRequest(volumeName,
-        bucketName, snapshotName1);
+    OMRequest omRequest = createSnapshotRequest(getVolumeName(),
+        getBucketName(), snapshotName1);
 
     OMException omException = assertThrows(OMException.class,
         () -> doPreExecute(omRequest));
@@ -181,29 +121,29 @@ public class TestOMSnapshotCreateRequest {
 
   @Test
   public void testValidateAndUpdateCache() throws Exception {
-    when(ozoneManager.isAdmin(any())).thenReturn(true);
-    OMRequest omRequest = createSnapshotRequest(volumeName,
-        bucketName, snapshotName1);
+    when(getOzoneManager().isAdmin(any())).thenReturn(true);
+    OMRequest omRequest = createSnapshotRequest(getVolumeName(),
+        getBucketName(), snapshotName1);
     OMSnapshotCreateRequest omSnapshotCreateRequest = doPreExecute(omRequest);
-    String key = getTableKey(volumeName, bucketName, snapshotName1);
-    String bucketKey = omMetadataManager.getBucketKey(volumeName, bucketName);
+    String key = getTableKey(getVolumeName(), getBucketName(), snapshotName1);
+    String bucketKey = getOmMetadataManager().getBucketKey(getVolumeName(), getBucketName());
 
     // Add a 1000-byte key to the bucket
     OmKeyInfo key1 = addKey("key-testValidateAndUpdateCache", 12345L);
     addKeyToTable(key1);
 
-    OmBucketInfo omBucketInfo = omMetadataManager.getBucketTable().get(
+    OmBucketInfo omBucketInfo = getOmMetadataManager().getBucketTable().get(
         bucketKey);
     long bucketDataSize = key1.getDataSize();
     long bucketUsedBytes = omBucketInfo.getUsedBytes();
     assertEquals(key1.getReplicatedSize(), bucketUsedBytes);
 
     // Value in cache should be null as of now.
-    assertNull(omMetadataManager.getSnapshotInfoTable().get(key));
+    assertNull(getOmMetadataManager().getSnapshotInfoTable().get(key));
 
     // Run validateAndUpdateCache.
     OMClientResponse omClientResponse =
-        omSnapshotCreateRequest.validateAndUpdateCache(ozoneManager, 1);
+        omSnapshotCreateRequest.validateAndUpdateCache(getOzoneManager(), 1);
 
     assertNotNull(omClientResponse.getOMResponse());
 
@@ -227,21 +167,21 @@ public class TestOMSnapshotCreateRequest {
 
     // Get value from cache
     SnapshotInfo snapshotInfoInCache =
-        omMetadataManager.getSnapshotInfoTable().get(key);
+        getOmMetadataManager().getSnapshotInfoTable().get(key);
     assertNotNull(snapshotInfoInCache);
     assertEquals(snapshotInfoFromProto, snapshotInfoInCache);
     assertEquals(snapshotInfoInCache.getLastTransactionInfo(),
         TransactionInfo.valueOf(TransactionInfo.getTermIndex(1L)).toByteString());
-    assertEquals(0, omMetrics.getNumSnapshotCreateFails());
-    assertEquals(1, omMetrics.getNumSnapshotActive());
-    assertEquals(1, omMetrics.getNumSnapshotCreates());
+    assertEquals(0, getOmMetrics().getNumSnapshotCreateFails());
+    assertEquals(1, getOmMetrics().getNumSnapshotActive());
+    assertEquals(1, getOmMetrics().getNumSnapshotCreates());
   }
 
   @Test
   public void testEntryRenamedKeyTable() throws Exception {
-    when(ozoneManager.isAdmin(any())).thenReturn(true);
+    when(getOzoneManager().isAdmin(any())).thenReturn(true);
     Table<String, String> snapshotRenamedTable =
-        omMetadataManager.getSnapshotRenamedTable();
+        getOmMetadataManager().getSnapshotRenamedTable();
 
     renameKey("key1", "key2", 0);
     renameDir("dir1", "dir2", 5);
@@ -251,17 +191,17 @@ public class TestOMSnapshotCreateRequest {
 
     // Create snapshot
     createSnapshot(snapshotName1);
-    String snapKey = getTableKey(volumeName,
-        bucketName, snapshotName1);
+    String snapKey = getTableKey(getVolumeName(),
+        getBucketName(), snapshotName1);
     SnapshotInfo snapshotInfo =
-        omMetadataManager.getSnapshotInfoTable().get(snapKey);
+        getOmMetadataManager().getSnapshotInfoTable().get(snapKey);
     assertNotNull(snapshotInfo);
 
     renameKey("key3", "key4", 10);
     renameDir("dir3", "dir4", 15);
 
     // Rename table should have two entries as rename is within snapshot scope.
-    assertEquals(2, omMetadataManager
+    assertEquals(2, getOmMetadataManager()
         .countRowsInTable(snapshotRenamedTable));
 
     // Create snapshot to clear snapshotRenamedTable
@@ -271,33 +211,33 @@ public class TestOMSnapshotCreateRequest {
 
   @Test
   public void testEntryExists() throws Exception {
-    when(ozoneManager.isAdmin(any())).thenReturn(true);
+    when(getOzoneManager().isAdmin(any())).thenReturn(true);
 
-    String key = getTableKey(volumeName, bucketName, snapshotName1);
+    String key = getTableKey(getVolumeName(), getBucketName(), snapshotName1);
 
     OMRequest omRequest =
-        createSnapshotRequest(volumeName, bucketName, snapshotName1);
+        createSnapshotRequest(getVolumeName(), getBucketName(), snapshotName1);
     OMSnapshotCreateRequest omSnapshotCreateRequest = doPreExecute(omRequest);
 
-    assertNull(omMetadataManager.getSnapshotInfoTable().get(key));
-    omSnapshotCreateRequest.validateAndUpdateCache(ozoneManager, 1);
+    assertNull(getOmMetadataManager().getSnapshotInfoTable().get(key));
+    omSnapshotCreateRequest.validateAndUpdateCache(getOzoneManager(), 1);
 
-    assertNotNull(omMetadataManager.getSnapshotInfoTable().get(key));
+    assertNotNull(getOmMetadataManager().getSnapshotInfoTable().get(key));
 
     // Now try to create again to verify error
-    omRequest = createSnapshotRequest(volumeName, bucketName, snapshotName1);
+    omRequest = createSnapshotRequest(getVolumeName(), getBucketName(), snapshotName1);
     omSnapshotCreateRequest = doPreExecute(omRequest);
     OMClientResponse omClientResponse =
-        omSnapshotCreateRequest.validateAndUpdateCache(ozoneManager, 2);
+        omSnapshotCreateRequest.validateAndUpdateCache(getOzoneManager(), 2);
 
     OMResponse omResponse = omClientResponse.getOMResponse();
     assertNotNull(omResponse.getCreateSnapshotResponse());
     assertEquals(OzoneManagerProtocolProtos.Status.FILE_ALREADY_EXISTS,
         omResponse.getStatus());
 
-    assertEquals(1, omMetrics.getNumSnapshotCreateFails());
-    assertEquals(1, omMetrics.getNumSnapshotActive());
-    assertEquals(2, omMetrics.getNumSnapshotCreates());
+    assertEquals(1, getOmMetrics().getNumSnapshotCreateFails());
+    assertEquals(1, getOmMetrics().getNumSnapshotActive());
+    assertEquals(2, getOmMetrics().getNumSnapshotCreates());
   }
 
   private void renameKey(String fromKey, String toKey, long offset)
@@ -316,15 +256,15 @@ public class TestOMSnapshotCreateRequest {
         new OMKeyRenameResponse(omResponse, fromKeyInfo.getKeyName(),
             toKeyInfo.getKeyName(), toKeyInfo);
 
-    omKeyRenameResponse.addToDBBatch(omMetadataManager, batchOperation);
-    omMetadataManager.getStore().commitBatchOperation(batchOperation);
+    omKeyRenameResponse.addToDBBatch(getOmMetadataManager(), getBatchOperation());
+    getOmMetadataManager().getStore().commitBatchOperation(getBatchOperation());
   }
 
   private void renameDir(String fromKey, String toKey, long offset)
       throws Exception {
     String fromKeyParentName = UUID.randomUUID().toString();
-    OmKeyInfo fromKeyParent = OMRequestTestUtils.createOmKeyInfo(volumeName,
-            bucketName, fromKeyParentName, RatisReplicationConfig.getInstance(THREE))
+    OmKeyInfo fromKeyParent = OMRequestTestUtils.createOmKeyInfo(getVolumeName(),
+            getBucketName(), fromKeyParentName, RatisReplicationConfig.getInstance(THREE))
         .setObjectID(100L)
         .build();
 
@@ -342,32 +282,32 @@ public class TestOMSnapshotCreateRequest {
         new OMKeyRenameResponseWithFSO(omResponse, getDBKeyName(fromKeyInfo),
             getDBKeyName(toKeyInfo), fromKeyParent, null, toKeyInfo,
             null, true, BucketLayout.FILE_SYSTEM_OPTIMIZED);
-    omKeyRenameResponse.addToDBBatch(omMetadataManager, batchOperation);
-    omMetadataManager.getStore().commitBatchOperation(batchOperation);
+    omKeyRenameResponse.addToDBBatch(getOmMetadataManager(), getBatchOperation());
+    getOmMetadataManager().getStore().commitBatchOperation(getBatchOperation());
   }
 
   protected String getDBKeyName(OmKeyInfo keyInfo) throws IOException {
-    return omMetadataManager.getOzonePathKey(
-        omMetadataManager.getVolumeId(volumeName),
-        omMetadataManager.getBucketId(volumeName, bucketName),
+    return getOmMetadataManager().getOzonePathKey(
+        getOmMetadataManager().getVolumeId(getVolumeName()),
+        getOmMetadataManager().getBucketId(getVolumeName(), getBucketName()),
         keyInfo.getParentObjectID(), keyInfo.getKeyName());
   }
 
   private void createSnapshot(String snapName) throws Exception {
     OMRequest omRequest =
         createSnapshotRequest(
-            volumeName, bucketName, snapName);
+            getVolumeName(), getBucketName(), snapName);
     OMSnapshotCreateRequest omSnapshotCreateRequest = doPreExecute(omRequest);
     //create entry
     OMClientResponse omClientResponse =
-        omSnapshotCreateRequest.validateAndUpdateCache(ozoneManager, 1);
-    omClientResponse.checkAndUpdateDB(omMetadataManager, batchOperation);
-    omMetadataManager.getStore().commitBatchOperation(batchOperation);
+        omSnapshotCreateRequest.validateAndUpdateCache(getOzoneManager(), 1);
+    omClientResponse.checkAndUpdateDB(getOmMetadataManager(), getBatchOperation());
+    getOmMetadataManager().getStore().commitBatchOperation(getBatchOperation());
   }
 
   private OMSnapshotCreateRequest doPreExecute(
       OMRequest originalRequest) throws Exception {
-    return doPreExecute(originalRequest, ozoneManager);
+    return doPreExecute(originalRequest, getOzoneManager());
   }
 
   /**
@@ -384,15 +324,15 @@ public class TestOMSnapshotCreateRequest {
   }
 
   private OmKeyInfo addKey(String keyName, long objectId) {
-    return OMRequestTestUtils.createOmKeyInfo(volumeName, bucketName, keyName,
+    return OMRequestTestUtils.createOmKeyInfo(getVolumeName(), getBucketName(), keyName,
             RatisReplicationConfig.getInstance(THREE)).setObjectID(objectId)
         .build();
   }
 
   protected String addKeyToTable(OmKeyInfo keyInfo) throws Exception {
     OMRequestTestUtils.addKeyToTable(false, true, keyInfo, 0, 0L,
-        omMetadataManager);
-    return omMetadataManager.getOzoneKey(keyInfo.getVolumeName(),
+        getOmMetadataManager());
+    return getOmMetadataManager().getOzoneKey(keyInfo.getVolumeName(),
         keyInfo.getBucketName(), keyInfo.getKeyName());
   }
 }
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotDeleteRequest.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotDeleteRequest.java
index 5a8bb5d7c0..4c5dc2e77f 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotDeleteRequest.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotDeleteRequest.java
@@ -19,33 +19,21 @@
 
 package org.apache.hadoop.ozone.om.request.snapshot;
 
-import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
-import org.apache.hadoop.ozone.audit.AuditLogger;
-import org.apache.hadoop.ozone.audit.AuditMessage;
-import org.apache.hadoop.ozone.om.OMConfigKeys;
-import org.apache.hadoop.ozone.om.OMMetrics;
-import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
-import org.apache.hadoop.ozone.om.OmSnapshotManager;
-import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
-import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
 import org.apache.hadoop.ozone.om.response.OMClientResponse;
-import org.apache.hadoop.ozone.om.upgrade.OMLayoutVersionManager;
+import org.apache.hadoop.ozone.om.snapshot.TestSnapshotRequestAndResponse;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status;
 import org.apache.hadoop.util.Time;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
-import java.io.File;
 import java.util.UUID;
 
 import static org.apache.hadoop.ozone.om.helpers.SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE;
@@ -61,10 +49,6 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.framework;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 /**
@@ -72,60 +56,15 @@ import static org.mockito.Mockito.when;
  * Mostly mirrors TestOMSnapshotCreateRequest.
  * testEntryNotExist() and testEntryExists() are unique.
  */
-public class TestOMSnapshotDeleteRequest {
-  @TempDir
-  private File folder;
+public class TestOMSnapshotDeleteRequest extends TestSnapshotRequestAndResponse {
 
-  private OzoneManager ozoneManager;
-  private OMMetrics omMetrics;
-  private OmMetadataManagerImpl omMetadataManager;
-
-  private String volumeName;
-  private String bucketName;
   private String snapshotName;
 
   @BeforeEach
   public void setup() throws Exception {
-    ozoneManager = mock(OzoneManager.class);
-    omMetrics = OMMetrics.create();
-    OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
-    ozoneConfiguration.set(OMConfigKeys.OZONE_OM_DB_DIRS,
-        folder.getAbsolutePath());
-    omMetadataManager = new OmMetadataManagerImpl(ozoneConfiguration,
-        ozoneManager);
-    when(ozoneManager.getMetrics()).thenReturn(omMetrics);
-    when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
-    when(ozoneManager.isRatisEnabled()).thenReturn(true);
-    when(ozoneManager.isFilesystemSnapshotEnabled()).thenReturn(true);
-    when(ozoneManager.isAdmin(any())).thenReturn(false);
-    when(ozoneManager.isOwner(any(), any())).thenReturn(false);
-    when(ozoneManager.getBucketOwner(any(), any(),
-        any(), any())).thenReturn("dummyBucketOwner");
-    OMLayoutVersionManager lvm = mock(OMLayoutVersionManager.class);
-    when(lvm.isAllowed(anyString())).thenReturn(true);
-    when(ozoneManager.getVersionManager()).thenReturn(lvm);
-    AuditLogger auditLogger = mock(AuditLogger.class);
-    when(ozoneManager.getAuditLogger()).thenReturn(auditLogger);
-    doNothing().when(auditLogger).logWrite(any(AuditMessage.class));
-
-    OmSnapshotManager omSnapshotManager = mock(OmSnapshotManager.class);
-    when(ozoneManager.getOmSnapshotManager()).thenReturn(omSnapshotManager);
-
-    volumeName = UUID.randomUUID().toString();
-    bucketName = UUID.randomUUID().toString();
     snapshotName = UUID.randomUUID().toString();
-    OMRequestTestUtils.addVolumeAndBucketToDB(
-        volumeName, bucketName, omMetadataManager);
-
   }
 
-  @AfterEach
-  public void stop() {
-    omMetrics.unRegister();
-    framework().clearInlineMocks();
-  }
-
-
   @ValueSource(strings = {
       // '-' is allowed.
       "9cdf0e8a-6946-41ad-a2d1-9eb724fab126",
@@ -136,9 +75,9 @@ public class TestOMSnapshotDeleteRequest {
   })
   @ParameterizedTest
   public void testPreExecute(String deleteSnapshotName) throws Exception {
-    when(ozoneManager.isOwner(any(), any())).thenReturn(true);
-    OMRequest omRequest = deleteSnapshotRequest(volumeName,
-        bucketName, deleteSnapshotName);
+    when(getOzoneManager().isOwner(any(), any())).thenReturn(true);
+    OMRequest omRequest = deleteSnapshotRequest(getVolumeName(),
+        getBucketName(), deleteSnapshotName);
     doPreExecute(omRequest);
   }
 
@@ -154,9 +93,9 @@ public class TestOMSnapshotDeleteRequest {
   })
   @ParameterizedTest
   public void testPreExecuteFailure(String deleteSnapshotName) {
-    when(ozoneManager.isOwner(any(), any())).thenReturn(true);
-    OMRequest omRequest = deleteSnapshotRequest(volumeName,
-        bucketName, deleteSnapshotName);
+    when(getOzoneManager().isOwner(any(), any())).thenReturn(true);
+    OMRequest omRequest = deleteSnapshotRequest(getVolumeName(),
+        getBucketName(), deleteSnapshotName);
     OMException omException =
         assertThrows(OMException.class, () -> doPreExecute(omRequest));
     assertTrue(omException.getMessage()
@@ -166,8 +105,8 @@ public class TestOMSnapshotDeleteRequest {
   @Test
   public void testPreExecuteBadOwner() {
     // Owner is not set for the request.
-    OMRequest omRequest = deleteSnapshotRequest(volumeName,
-        bucketName, snapshotName);
+    OMRequest omRequest = deleteSnapshotRequest(getVolumeName(),
+        getBucketName(), snapshotName);
 
     OMException omException = assertThrows(OMException.class,
         () -> doPreExecute(omRequest));
@@ -177,27 +116,27 @@ public class TestOMSnapshotDeleteRequest {
 
   @Test
   public void testValidateAndUpdateCache() throws Exception {
-    when(ozoneManager.isAdmin(any())).thenReturn(true);
+    when(getOzoneManager().isAdmin(any())).thenReturn(true);
     OMRequest omRequest =
-        deleteSnapshotRequest(volumeName, bucketName, snapshotName);
+        deleteSnapshotRequest(getVolumeName(), getBucketName(), snapshotName);
     OMSnapshotDeleteRequest omSnapshotDeleteRequest = doPreExecute(omRequest);
-    String key = SnapshotInfo.getTableKey(volumeName, bucketName, snapshotName);
+    String key = SnapshotInfo.getTableKey(getVolumeName(), getBucketName(), snapshotName);
 
     // As we have not still called validateAndUpdateCache, get() should
     // return null.
-    assertNull(omMetadataManager.getSnapshotInfoTable().get(key));
+    assertNull(getOmMetadataManager().getSnapshotInfoTable().get(key));
 
     // add key to cache
-    SnapshotInfo snapshotInfo = SnapshotInfo.newInstance(volumeName, bucketName,
+    SnapshotInfo snapshotInfo = SnapshotInfo.newInstance(getVolumeName(), getBucketName(),
         snapshotName, null, Time.now());
     assertEquals(SNAPSHOT_ACTIVE, snapshotInfo.getSnapshotStatus());
-    omMetadataManager.getSnapshotInfoTable().addCacheEntry(
+    getOmMetadataManager().getSnapshotInfoTable().addCacheEntry(
         new CacheKey<>(key),
         CacheValue.get(1L, snapshotInfo));
 
     // Trigger validateAndUpdateCache
     OMClientResponse omClientResponse =
-        omSnapshotDeleteRequest.validateAndUpdateCache(ozoneManager, 2L);
+        omSnapshotDeleteRequest.validateAndUpdateCache(getOzoneManager(), 2L);
 
     OMResponse omResponse = omClientResponse.getOMResponse();
     assertNotNull(omResponse);
@@ -207,14 +146,14 @@ public class TestOMSnapshotDeleteRequest {
     assertEquals(OK, omResponse.getStatus());
 
     // check cache
-    snapshotInfo = omMetadataManager.getSnapshotInfoTable().get(key);
+    snapshotInfo = getOmMetadataManager().getSnapshotInfoTable().get(key);
     assertNotNull(snapshotInfo);
     assertEquals(SNAPSHOT_DELETED, snapshotInfo.getSnapshotStatus());
-    assertEquals(0, omMetrics.getNumSnapshotCreates());
+    assertEquals(0, getOmMetrics().getNumSnapshotCreates());
     // Expected -1 because no snapshot was created before.
-    assertEquals(-1, omMetrics.getNumSnapshotActive());
-    assertEquals(1, omMetrics.getNumSnapshotDeleted());
-    assertEquals(0, omMetrics.getNumSnapshotDeleteFails());
+    assertEquals(-1, getOmMetrics().getNumSnapshotActive());
+    assertEquals(1, getOmMetrics().getNumSnapshotDeleted());
+    assertEquals(0, getOmMetrics().getNumSnapshotDeleteFails());
   }
 
   /**
@@ -222,25 +161,25 @@ public class TestOMSnapshotDeleteRequest {
    */
   @Test
   public void testEntryNotExist() throws Exception {
-    when(ozoneManager.isAdmin(any())).thenReturn(true);
+    when(getOzoneManager().isAdmin(any())).thenReturn(true);
     OMRequest omRequest = deleteSnapshotRequest(
-        volumeName, bucketName, snapshotName);
+        getVolumeName(), getBucketName(), snapshotName);
     OMSnapshotDeleteRequest omSnapshotDeleteRequest = doPreExecute(omRequest);
-    String key = SnapshotInfo.getTableKey(volumeName, bucketName, snapshotName);
+    String key = SnapshotInfo.getTableKey(getVolumeName(), getBucketName(), snapshotName);
 
     // Entry does not exist
-    assertNull(omMetadataManager.getSnapshotInfoTable().get(key));
+    assertNull(getOmMetadataManager().getSnapshotInfoTable().get(key));
 
     // Trigger delete snapshot validateAndUpdateCache
     OMClientResponse omClientResponse =
-        omSnapshotDeleteRequest.validateAndUpdateCache(ozoneManager, 1L);
+        omSnapshotDeleteRequest.validateAndUpdateCache(getOzoneManager(), 1L);
 
     OMResponse omResponse = omClientResponse.getOMResponse();
     assertNotNull(omResponse.getDeleteSnapshotResponse());
     assertEquals(Status.FILE_NOT_FOUND, omResponse.getStatus());
-    assertEquals(0, omMetrics.getNumSnapshotActive());
-    assertEquals(0, omMetrics.getNumSnapshotDeleted());
-    assertEquals(1, omMetrics.getNumSnapshotDeleteFails());
+    assertEquals(0, getOmMetrics().getNumSnapshotActive());
+    assertEquals(0, getOmMetrics().getNumSnapshotDeleted());
+    assertEquals(1, getOmMetrics().getNumSnapshotDeleteFails());
   }
 
   /**
@@ -249,50 +188,50 @@ public class TestOMSnapshotDeleteRequest {
    */
   @Test
   public void testEntryExist() throws Exception {
-    when(ozoneManager.isAdmin(any())).thenReturn(true);
-    String key = SnapshotInfo.getTableKey(volumeName, bucketName, snapshotName);
+    when(getOzoneManager().isAdmin(any())).thenReturn(true);
+    String key = SnapshotInfo.getTableKey(getVolumeName(), getBucketName(), snapshotName);
 
     OMRequest omRequest1 =
-        createSnapshotRequest(volumeName, bucketName, snapshotName);
+        createSnapshotRequest(getVolumeName(), getBucketName(), snapshotName);
     OMSnapshotCreateRequest omSnapshotCreateRequest =
-        TestOMSnapshotCreateRequest.doPreExecute(omRequest1, ozoneManager);
+        TestOMSnapshotCreateRequest.doPreExecute(omRequest1, getOzoneManager());
 
-    assertNull(omMetadataManager.getSnapshotInfoTable().get(key));
+    assertNull(getOmMetadataManager().getSnapshotInfoTable().get(key));
 
     // Create snapshot entry
-    omSnapshotCreateRequest.validateAndUpdateCache(ozoneManager, 1L);
+    omSnapshotCreateRequest.validateAndUpdateCache(getOzoneManager(), 1L);
     SnapshotInfo snapshotInfo =
-        omMetadataManager.getSnapshotInfoTable().get(key);
+        getOmMetadataManager().getSnapshotInfoTable().get(key);
     assertNotNull(snapshotInfo);
     assertEquals(SNAPSHOT_ACTIVE, snapshotInfo.getSnapshotStatus());
 
-    assertEquals(1, omMetrics.getNumSnapshotActive());
+    assertEquals(1, getOmMetrics().getNumSnapshotActive());
     OMRequest omRequest2 =
-        deleteSnapshotRequest(volumeName, bucketName, snapshotName);
+        deleteSnapshotRequest(getVolumeName(), getBucketName(), snapshotName);
     OMSnapshotDeleteRequest omSnapshotDeleteRequest = doPreExecute(omRequest2);
 
     // Delete snapshot entry
     OMClientResponse omClientResponse =
-        omSnapshotDeleteRequest.validateAndUpdateCache(ozoneManager, 2L);
+        omSnapshotDeleteRequest.validateAndUpdateCache(getOzoneManager(), 2L);
     // Response should be successful
     OMResponse omResponse = omClientResponse.getOMResponse();
     assertNotNull(omResponse);
     assertNotNull(omResponse.getDeleteSnapshotResponse());
     assertEquals(OK, omResponse.getStatus());
 
-    snapshotInfo = omMetadataManager.getSnapshotInfoTable().get(key);
+    snapshotInfo = getOmMetadataManager().getSnapshotInfoTable().get(key);
     // The snapshot entry should still exist in the table,
     // but marked as DELETED.
     assertNotNull(snapshotInfo);
     assertEquals(SNAPSHOT_DELETED, snapshotInfo.getSnapshotStatus());
     assertThat(snapshotInfo.getDeletionTime()).isGreaterThan(0L);
-    assertEquals(0, omMetrics.getNumSnapshotActive());
+    assertEquals(0, getOmMetrics().getNumSnapshotActive());
 
     // Now delete snapshot entry again, expect error.
-    omRequest2 = deleteSnapshotRequest(volumeName, bucketName, snapshotName);
+    omRequest2 = deleteSnapshotRequest(getVolumeName(), getBucketName(), snapshotName);
     omSnapshotDeleteRequest = doPreExecute(omRequest2);
     omClientResponse =
-        omSnapshotDeleteRequest.validateAndUpdateCache(ozoneManager, 3L);
+        omSnapshotDeleteRequest.validateAndUpdateCache(getOzoneManager(), 3L);
 
     omResponse = omClientResponse.getOMResponse();
     assertNotNull(omResponse);
@@ -300,11 +239,11 @@ public class TestOMSnapshotDeleteRequest {
     assertEquals(Status.FILE_NOT_FOUND, omResponse.getStatus());
 
     // Snapshot entry should still be there.
-    snapshotInfo = omMetadataManager.getSnapshotInfoTable().get(key);
+    snapshotInfo = getOmMetadataManager().getSnapshotInfoTable().get(key);
     assertNotNull(snapshotInfo);
     assertEquals(SNAPSHOT_DELETED, snapshotInfo.getSnapshotStatus());
-    assertEquals(0, omMetrics.getNumSnapshotActive());
-    assertEquals(1, omMetrics.getNumSnapshotDeleteFails());
+    assertEquals(0, getOmMetrics().getNumSnapshotActive());
+    assertEquals(1, getOmMetrics().getNumSnapshotDeleteFails());
   }
 
   private OMSnapshotDeleteRequest doPreExecute(
@@ -313,7 +252,7 @@ public class TestOMSnapshotDeleteRequest {
         new OMSnapshotDeleteRequest(originalRequest);
 
     OMRequest modifiedRequest =
-        omSnapshotDeleteRequest.preExecute(ozoneManager);
+        omSnapshotDeleteRequest.preExecute(getOzoneManager());
     return new OMSnapshotDeleteRequest(modifiedRequest);
   }
 
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotMoveTableKeysRequest.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotMoveTableKeysRequest.java
new file mode 100644
index 0000000000..247f322dfc
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotMoveTableKeysRequest.java
@@ -0,0 +1,264 @@
+/*
+ * 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.om.request.snapshot;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.om.snapshot.SnapshotUtils;
+import org.apache.hadoop.ozone.om.snapshot.TestSnapshotRequestAndResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_KEY_NAME;
+import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_REQUEST;
+import static org.apache.hadoop.ozone.om.request.OMRequestTestUtils.addVolumeAndBucketToDB;
+import static org.apache.hadoop.ozone.om.request.OMRequestTestUtils.deleteSnapshotRequest;
+import static org.apache.hadoop.ozone.om.request.OMRequestTestUtils.moveSnapshotTableKeyRequest;
+
+/**
+ * Class to test OmSnapshotMoveTableKeyRequest.
+ */
+public class TestOMSnapshotMoveTableKeysRequest extends TestSnapshotRequestAndResponse {
+
+  private String snapshotName1;
+  private String snapshotName2;
+  private SnapshotInfo snapshotInfo1;
+  private SnapshotInfo snapshotInfo2;
+
+  @BeforeEach
+  public void setup() throws Exception {
+    snapshotName1 = UUID.randomUUID().toString();
+    snapshotName2 = UUID.randomUUID().toString();
+  }
+
+  public TestOMSnapshotMoveTableKeysRequest() {
+    super(true);
+  }
+
+  private void createSnapshots(boolean createSecondSnapshot) throws Exception {
+    createSnapshotCheckpoint(getVolumeName(), getBucketName(), snapshotName1);
+    snapshotInfo1 = SnapshotUtils.getSnapshotInfo(getOzoneManager(), getVolumeName(), getBucketName(), snapshotName1);
+    if (createSecondSnapshot) {
+      createSnapshotCheckpoint(getVolumeName(), getBucketName(), snapshotName2);
+      snapshotInfo2 = SnapshotUtils.getSnapshotInfo(getOzoneManager(), getVolumeName(), getBucketName(), snapshotName2);
+    }
+  }
+
+  private SnapshotInfo deleteSnapshot(SnapshotInfo snapshotInfo, long transactionIndex) throws Exception {
+    OzoneManagerProtocolProtos.OMRequest omRequest = deleteSnapshotRequest(snapshotInfo.getVolumeName(),
+        snapshotInfo.getBucketName(), snapshotInfo.getName());
+    OMSnapshotDeleteRequest omSnapshotDeleteRequest = new OMSnapshotDeleteRequest(omRequest);
+    omSnapshotDeleteRequest.preExecute(getOzoneManager());
+    omSnapshotDeleteRequest.validateAndUpdateCache(getOzoneManager(), transactionIndex);
+    return SnapshotUtils.getSnapshotInfo(getOzoneManager(), snapshotInfo.getTableKey());
+  }
+
+  @Test
+  public void testValidateAndUpdateCacheWithNextSnapshotInactive() throws Exception {
+    createSnapshots(true);
+    snapshotInfo2 = deleteSnapshot(snapshotInfo2, 0);
+    OzoneManagerProtocolProtos.OMRequest omRequest = moveSnapshotTableKeyRequest(snapshotInfo1.getSnapshotId(),
+        Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
+    OMSnapshotMoveTableKeysRequest omSnapshotMoveTableKeysRequest = new OMSnapshotMoveTableKeysRequest(omRequest);
+    omSnapshotMoveTableKeysRequest = new OMSnapshotMoveTableKeysRequest(
+        omSnapshotMoveTableKeysRequest.preExecute(getOzoneManager()));
+    OMClientResponse omClientResponse = omSnapshotMoveTableKeysRequest.validateAndUpdateCache(getOzoneManager(), 1);
+    Assertions.assertFalse(omClientResponse.getOMResponse().getSuccess());
+    Assertions.assertEquals(OzoneManagerProtocolProtos.Status.INVALID_SNAPSHOT_ERROR,
+        omClientResponse.getOMResponse().getStatus());
+  }
+
+  @Test
+  public void testPreExecuteWithInvalidDeletedKeyPrefix() throws Exception {
+    createSnapshots(true);
+    String invalidVolumeName = UUID.randomUUID().toString();
+    String invalidBucketName = UUID.randomUUID().toString();
+    addVolumeAndBucketToDB(invalidVolumeName, invalidBucketName, getOmMetadataManager());
+    List<Pair<String, List<OmKeyInfo>>> deletedKeys =
+        Stream.of(getDeletedKeys(getVolumeName(), getBucketName(), 0, 10, 10, 0),
+                getDeletedKeys(invalidVolumeName, invalidBucketName, 0, 10, 10, 0))
+            .flatMap(List::stream).collect(Collectors.toList());
+    OzoneManagerProtocolProtos.OMRequest omRequest = moveSnapshotTableKeyRequest(snapshotInfo1.getSnapshotId(),
+        deletedKeys, Collections.emptyList(), Collections.emptyList());
+    OMSnapshotMoveTableKeysRequest omSnapshotMoveTableKeysRequest = new OMSnapshotMoveTableKeysRequest(omRequest);
+    OMException omException = Assertions.assertThrows(OMException.class,
+        () -> omSnapshotMoveTableKeysRequest.preExecute(getOzoneManager()));
+    Assertions.assertEquals(INVALID_KEY_NAME, omException.getResult());
+  }
+
+  @Test
+  public void testPreExecuteWithInvalidDeletedDirPrefix() throws Exception {
+    createSnapshots(true);
+    String invalidVolumeName = UUID.randomUUID().toString();
+    String invalidBucketName = UUID.randomUUID().toString();
+    addVolumeAndBucketToDB(invalidVolumeName, invalidBucketName, getOmMetadataManager());
+    List<Pair<String, List<OmKeyInfo>>> deletedDirs =
+        Stream.of(getDeletedDirKeys(getVolumeName(), getBucketName(), 0, 10, 1),
+                getDeletedDirKeys(invalidVolumeName, invalidBucketName, 0, 10, 1))
+            .flatMap(List::stream).collect(Collectors.toList());
+    OzoneManagerProtocolProtos.OMRequest omRequest = moveSnapshotTableKeyRequest(snapshotInfo1.getSnapshotId(),
+        Collections.emptyList(), deletedDirs, Collections.emptyList());
+    OMSnapshotMoveTableKeysRequest omSnapshotMoveTableKeysRequest = new OMSnapshotMoveTableKeysRequest(omRequest);
+    OMException omException = Assertions.assertThrows(OMException.class,
+        () -> omSnapshotMoveTableKeysRequest.preExecute(getOzoneManager()));
+    Assertions.assertEquals(INVALID_KEY_NAME, omException.getResult());
+  }
+
+  @Test
+  public void testPreExecuteWithInvalidNumberKeys() throws Exception {
+    createSnapshots(true);
+    String invalidVolumeName = UUID.randomUUID().toString();
+    String invalidBucketName = UUID.randomUUID().toString();
+    addVolumeAndBucketToDB(invalidVolumeName, invalidBucketName, getOmMetadataManager());
+    List<Pair<String, List<OmKeyInfo>>> deletedDirs =
+        Stream.of(getDeletedDirKeys(getVolumeName(), getBucketName(), 0, 10, 1),
+                getDeletedDirKeys(invalidVolumeName, invalidBucketName, 0, 10, 10))
+            .flatMap(List::stream).collect(Collectors.toList());
+    List<Pair<String, List<OmKeyInfo>>> deletedKeys =
+        Stream.of(getDeletedKeys(getVolumeName(), getBucketName(), 0, 10, 10, 0),
+            getDeletedKeys(invalidVolumeName, invalidBucketName, 0, 10, 0, 0))
+        .flatMap(List::stream).collect(Collectors.toList());
+    List<Pair<String, String>> renameKeys = getRenameKeys(getVolumeName(), getBucketName(), 0, 10, snapshotName1);
+    renameKeys.add(Pair.of(getOmMetadataManager().getRenameKey(getVolumeName(), getBucketName(), 11), null));
+    OzoneManagerProtocolProtos.OMRequest omRequest = moveSnapshotTableKeyRequest(snapshotInfo1.getSnapshotId(),
+        deletedKeys, deletedDirs, renameKeys);
+    OMSnapshotMoveTableKeysRequest omSnapshotMoveTableKeysRequest = new OMSnapshotMoveTableKeysRequest(omRequest);
+    omRequest = omSnapshotMoveTableKeysRequest.preExecute(getOzoneManager());
+    for (OzoneManagerProtocolProtos.SnapshotMoveKeyInfos deletedDir :
+         omRequest.getSnapshotMoveTableKeysRequest().getDeletedDirsList()) {
+      Assertions.assertEquals(1, deletedDir.getKeyInfosList().size());
+    }
+
+    for (OzoneManagerProtocolProtos.SnapshotMoveKeyInfos deletedKey :
+        omRequest.getSnapshotMoveTableKeysRequest().getDeletedKeysList()) {
+      Assertions.assertNotEquals(0, deletedKey.getKeyInfosList().size());
+    }
+
+    for (HddsProtos.KeyValue renameKey : omRequest.getSnapshotMoveTableKeysRequest().getRenamedKeysList()) {
+      Assertions.assertTrue(renameKey.hasKey() && renameKey.hasValue());
+    }
+
+  }
+
+  @Test
+  public void testPreExecuteWithInvalidRenamePrefix() throws Exception {
+    createSnapshots(true);
+    String invalidVolumeName = UUID.randomUUID().toString();
+    String invalidBucketName = UUID.randomUUID().toString();
+    addVolumeAndBucketToDB(invalidVolumeName, invalidBucketName, getOmMetadataManager());
+    List<Pair<String, String>> renameKeys =
+        Stream.of(getRenameKeys(getVolumeName(), getBucketName(), 0, 10, snapshotName1),
+                getRenameKeys(invalidVolumeName, invalidBucketName, 0, 10, snapshotName2)).flatMap(List::stream)
+            .collect(Collectors.toList());
+    OzoneManagerProtocolProtos.OMRequest omRequest = moveSnapshotTableKeyRequest(snapshotInfo1.getSnapshotId(),
+        Collections.emptyList(), Collections.emptyList(), renameKeys);
+    OMSnapshotMoveTableKeysRequest omSnapshotMoveTableKeysRequest = new OMSnapshotMoveTableKeysRequest(omRequest);
+    OMException omException = Assertions.assertThrows(OMException.class,
+        () -> omSnapshotMoveTableKeysRequest.preExecute(getOzoneManager()));
+    Assertions.assertEquals(INVALID_KEY_NAME, omException.getResult());
+  }
+
+  @Test
+  public void testValidateAndUpdateCache() throws Exception {
+    createSnapshots(true);
+    String invalidVolumeName = UUID.randomUUID().toString();
+    String invalidBucketName = UUID.randomUUID().toString();
+    addVolumeAndBucketToDB(invalidVolumeName, invalidBucketName, getOmMetadataManager());
+    List<Pair<String, List<OmKeyInfo>>> deletedKeys = getDeletedKeys(getVolumeName(), getBucketName(), 0, 10, 10, 0);
+    List<Pair<String, List<OmKeyInfo>>> deletedDirs = getDeletedDirKeys(getVolumeName(), getBucketName(), 0, 10, 1);
+    List<Pair<String, String>> renameKeys = getRenameKeys(getVolumeName(), getBucketName(), 0, 10, snapshotName1);
+    OzoneManagerProtocolProtos.OMRequest omRequest = moveSnapshotTableKeyRequest(snapshotInfo1.getSnapshotId(),
+        deletedKeys, deletedDirs, renameKeys);
+    OMSnapshotMoveTableKeysRequest omSnapshotMoveTableKeysRequest = new OMSnapshotMoveTableKeysRequest(omRequest);
+    // perform preExecute.
+    omSnapshotMoveTableKeysRequest = new OMSnapshotMoveTableKeysRequest(
+        omSnapshotMoveTableKeysRequest.preExecute(getOzoneManager()));
+    OMClientResponse omClientResponse = omSnapshotMoveTableKeysRequest.validateAndUpdateCache(getOzoneManager(), 1);
+    Assertions.assertTrue(omClientResponse.getOMResponse().getSuccess());
+    Assertions.assertEquals(OzoneManagerProtocolProtos.Status.OK,
+        omClientResponse.getOMResponse().getStatus());
+  }
+
+  @Test
+  public void testPreExecuteWithInvalidDuplicateDeletedKey() throws Exception {
+    createSnapshots(true);
+    String invalidVolumeName = UUID.randomUUID().toString();
+    String invalidBucketName = UUID.randomUUID().toString();
+    addVolumeAndBucketToDB(invalidVolumeName, invalidBucketName, getOmMetadataManager());
+    List<Pair<String, List<OmKeyInfo>>> deletedKeys =
+        Stream.of(getDeletedKeys(getVolumeName(), getBucketName(), 0, 10, 10, 0),
+                getDeletedKeys(getVolumeName(), getBucketName(), 0, 10, 10, 0)).flatMap(List::stream)
+            .collect(Collectors.toList());
+    OzoneManagerProtocolProtos.OMRequest omRequest = moveSnapshotTableKeyRequest(snapshotInfo1.getSnapshotId(),
+        deletedKeys, Collections.emptyList(), Collections.emptyList());
+    OMSnapshotMoveTableKeysRequest omSnapshotMoveTableKeysRequest = new OMSnapshotMoveTableKeysRequest(omRequest);
+    OMException omException = Assertions.assertThrows(OMException.class,
+        () -> omSnapshotMoveTableKeysRequest.preExecute(getOzoneManager()));
+    Assertions.assertEquals(INVALID_REQUEST, omException.getResult());
+  }
+
+  @Test
+  public void testPreExecuteWithInvalidDuplicateDeletedDir() throws Exception {
+    createSnapshots(true);
+    String invalidVolumeName = UUID.randomUUID().toString();
+    String invalidBucketName = UUID.randomUUID().toString();
+    addVolumeAndBucketToDB(invalidVolumeName, invalidBucketName, getOmMetadataManager());
+    List<Pair<String, List<OmKeyInfo>>> deletedDirs =
+        Stream.of(getDeletedDirKeys(getVolumeName(), getBucketName(), 0, 10, 1),
+                getDeletedDirKeys(getVolumeName(), getBucketName(), 0, 10, 1)).flatMap(List::stream)
+            .collect(Collectors.toList());
+    OzoneManagerProtocolProtos.OMRequest omRequest = moveSnapshotTableKeyRequest(snapshotInfo1.getSnapshotId(),
+        Collections.emptyList(), deletedDirs, Collections.emptyList());
+    OMSnapshotMoveTableKeysRequest omSnapshotMoveTableKeysRequest = new OMSnapshotMoveTableKeysRequest(omRequest);
+    OMException omException = Assertions.assertThrows(OMException.class,
+        () -> omSnapshotMoveTableKeysRequest.preExecute(getOzoneManager()));
+    Assertions.assertEquals(INVALID_REQUEST, omException.getResult());
+  }
+
+  @Test
+  public void testPreExecuteWithInvalidDuplicateRenameKey() throws Exception {
+    createSnapshots(true);
+    String invalidVolumeName = UUID.randomUUID().toString();
+    String invalidBucketName = UUID.randomUUID().toString();
+    addVolumeAndBucketToDB(invalidVolumeName, invalidBucketName, getOmMetadataManager());
+    List<Pair<String, String>> renameKeys =
+        Stream.of(getRenameKeys(getVolumeName(), getBucketName(), 0, 10, snapshotName1),
+                getRenameKeys(getVolumeName(), getBucketName(), 0, 10, snapshotName1))
+            .flatMap(List::stream).collect(Collectors.toList());
+    OzoneManagerProtocolProtos.OMRequest omRequest = moveSnapshotTableKeyRequest(snapshotInfo1.getSnapshotId(),
+        Collections.emptyList(), Collections.emptyList(), renameKeys);
+    OMSnapshotMoveTableKeysRequest omSnapshotMoveTableKeysRequest = new OMSnapshotMoveTableKeysRequest(omRequest);
+    OMException omException = Assertions.assertThrows(OMException.class,
+        () -> omSnapshotMoveTableKeysRequest.preExecute(getOzoneManager()));
+    Assertions.assertEquals(INVALID_REQUEST, omException.getResult());
+  }
+}
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotPurgeRequestAndResponse.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotPurgeRequestAndResponse.java
index d0a5559a87..1c44decdfd 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotPurgeRequestAndResponse.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotPurgeRequestAndResponse.java
@@ -20,43 +20,28 @@
 package org.apache.hadoop.ozone.om.request.snapshot;
 
 import com.google.protobuf.ByteString;
-import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.utils.TransactionInfo;
 import org.apache.hadoop.hdds.utils.db.BatchOperation;
-import org.apache.hadoop.hdds.utils.db.RDBStore;
 import org.apache.hadoop.hdds.utils.db.Table;
-import org.apache.hadoop.ozone.OzoneConfigKeys;
-import org.apache.hadoop.ozone.audit.AuditLogger;
-import org.apache.hadoop.ozone.om.IOmMetadataReader;
-import org.apache.hadoop.ozone.om.OMConfigKeys;
-import org.apache.hadoop.ozone.om.OMMetadataManager;
-import org.apache.hadoop.ozone.om.OMMetrics;
 import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
-import org.apache.hadoop.ozone.om.OmSnapshotManager;
-import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.SnapshotChainManager;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
 import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
-import org.apache.hadoop.ozone.om.response.snapshot.OMSnapshotCreateResponse;
 import org.apache.hadoop.ozone.om.response.snapshot.OMSnapshotPurgeResponse;
-import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted;
-import org.apache.hadoop.ozone.om.upgrade.OMLayoutVersionManager;
+import org.apache.hadoop.ozone.om.snapshot.TestSnapshotRequestAndResponse;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotPurgeRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.junit.jupiter.params.provider.ValueSource;
 
-import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -71,10 +56,8 @@ import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -82,49 +65,16 @@ import static org.mockito.Mockito.when;
 /**
  * Tests OMSnapshotPurgeRequest class.
  */
-public class TestOMSnapshotPurgeRequestAndResponse {
-  private List<Path> checkpointPaths = new ArrayList<>();
-
-  private OzoneManager ozoneManager;
-  private OMMetrics omMetrics;
-  private OMMetadataManager omMetadataManager;
-  private OmSnapshotManager omSnapshotManager;
-  private AuditLogger auditLogger;
-
-  private String volumeName;
-  private String bucketName;
+public class TestOMSnapshotPurgeRequestAndResponse extends TestSnapshotRequestAndResponse {
+  private final List<Path> checkpointPaths = new ArrayList<>();
   private String keyName;
 
+  public TestOMSnapshotPurgeRequestAndResponse() {
+    super(true);
+  }
+
   @BeforeEach
-  void setup(@TempDir File testDir) throws Exception {
-    ozoneManager = mock(OzoneManager.class);
-    OMLayoutVersionManager lvm = mock(OMLayoutVersionManager.class);
-    when(lvm.isAllowed(anyString())).thenReturn(true);
-    when(ozoneManager.getVersionManager()).thenReturn(lvm);
-    when(ozoneManager.isRatisEnabled()).thenReturn(true);
-    auditLogger = mock(AuditLogger.class);
-    when(ozoneManager.getAuditLogger()).thenReturn(auditLogger);
-    omMetrics = OMMetrics.create();
-    OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
-    ozoneConfiguration.set(OMConfigKeys.OZONE_OM_DB_DIRS,
-        testDir.getAbsolutePath());
-    ozoneConfiguration.set(OzoneConfigKeys.OZONE_METADATA_DIRS,
-        testDir.getAbsolutePath());
-    omMetadataManager = new OmMetadataManagerImpl(ozoneConfiguration,
-        ozoneManager);
-    when(ozoneManager.getMetrics()).thenReturn(omMetrics);
-    when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
-    when(ozoneManager.getConfiguration()).thenReturn(ozoneConfiguration);
-    when(ozoneManager.isAdmin(any())).thenReturn(true);
-    when(ozoneManager.isFilesystemSnapshotEnabled()).thenReturn(true);
-
-    ReferenceCounted<IOmMetadataReader> rcOmMetadataReader =
-        mock(ReferenceCounted.class);
-    when(ozoneManager.getOmMetadataReader()).thenReturn(rcOmMetadataReader);
-    omSnapshotManager = new OmSnapshotManager(ozoneManager);
-    when(ozoneManager.getOmSnapshotManager()).thenReturn(omSnapshotManager);
-    volumeName = UUID.randomUUID().toString();
-    bucketName = UUID.randomUUID().toString();
+  public void setup() throws Exception {
     keyName = UUID.randomUUID().toString();
   }
 
@@ -135,17 +85,14 @@ public class TestOMSnapshotPurgeRequestAndResponse {
       throws Exception {
 
     Random random = new Random();
-    // Add volume, bucket and key entries to OM DB.
-    OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
-        omMetadataManager);
 
     // Create Snapshot and CheckpointDir
     List<String> purgeSnapshots = new ArrayList<>(numSnapshotKeys);
     for (int i = 1; i <= numSnapshotKeys; i++) {
       String snapshotName = keyName + "-" + random.nextLong();
       createSnapshotCheckpoint(snapshotName);
-      purgeSnapshots.add(SnapshotInfo.getTableKey(volumeName,
-          bucketName, snapshotName));
+      purgeSnapshots.add(SnapshotInfo.getTableKey(getVolumeName(),
+          getBucketName(), snapshotName));
     }
 
     return purgeSnapshots;
@@ -175,39 +122,7 @@ public class TestOMSnapshotPurgeRequestAndResponse {
    * Create snapshot and checkpoint directory.
    */
   private void createSnapshotCheckpoint(String snapshotName) throws Exception {
-    createSnapshotCheckpoint(volumeName, bucketName, snapshotName);
-  }
-
-  private void createSnapshotCheckpoint(String volume,
-                                        String bucket,
-                                        String snapshotName) throws Exception {
-    OMRequest omRequest = OMRequestTestUtils
-        .createSnapshotRequest(volume, bucket, snapshotName);
-    // Pre-Execute OMSnapshotCreateRequest.
-    OMSnapshotCreateRequest omSnapshotCreateRequest =
-        TestOMSnapshotCreateRequest.doPreExecute(omRequest, ozoneManager);
-
-    // validateAndUpdateCache OMSnapshotCreateResponse.
-    OMSnapshotCreateResponse omClientResponse = (OMSnapshotCreateResponse)
-        omSnapshotCreateRequest.validateAndUpdateCache(ozoneManager, 1);
-    // Add to batch and commit to DB.
-    try (BatchOperation batchOperation = omMetadataManager.getStore().initBatchOperation()) {
-      omClientResponse.addToDBBatch(omMetadataManager, batchOperation);
-      omMetadataManager.getStore().commitBatchOperation(batchOperation);
-    }
-
-    String key = SnapshotInfo.getTableKey(volume, bucket, snapshotName);
-    SnapshotInfo snapshotInfo =
-        omMetadataManager.getSnapshotInfoTable().get(key);
-    assertNotNull(snapshotInfo);
-
-    RDBStore store = (RDBStore) omMetadataManager.getStore();
-    String checkpointPrefix = store.getDbLocation().getName();
-    Path snapshotDirPath = Paths.get(store.getSnapshotsParentDir(),
-        checkpointPrefix + snapshotInfo.getCheckpointDir());
-    // Check the DB is still there
-    assertTrue(Files.exists(snapshotDirPath));
-    checkpointPaths.add(snapshotDirPath);
+    checkpointPaths.add(createSnapshotCheckpoint(getVolumeName(), getBucketName(), snapshotName));
   }
 
   private OMSnapshotPurgeRequest preExecute(OMRequest originalOmRequest)
@@ -215,7 +130,7 @@ public class TestOMSnapshotPurgeRequestAndResponse {
     OMSnapshotPurgeRequest omSnapshotPurgeRequest =
         new OMSnapshotPurgeRequest(originalOmRequest);
     OMRequest modifiedOmRequest = omSnapshotPurgeRequest
-        .preExecute(ozoneManager);
+        .preExecute(getOzoneManager());
     return new OMSnapshotPurgeRequest(modifiedOmRequest);
   }
 
@@ -227,48 +142,48 @@ public class TestOMSnapshotPurgeRequestAndResponse {
 
     // validateAndUpdateCache for OMSnapshotPurgeRequest.
     OMSnapshotPurgeResponse omSnapshotPurgeResponse = (OMSnapshotPurgeResponse)
-        omSnapshotPurgeRequest.validateAndUpdateCache(ozoneManager, 200L);
+        omSnapshotPurgeRequest.validateAndUpdateCache(getOzoneManager(), 200L);
 
     // Commit to DB.
-    try (BatchOperation batchOperation = omMetadataManager.getStore().initBatchOperation()) {
-      omSnapshotPurgeResponse.checkAndUpdateDB(omMetadataManager, batchOperation);
-      omMetadataManager.getStore().commitBatchOperation(batchOperation);
+    try (BatchOperation batchOperation = getOmMetadataManager().getStore().initBatchOperation()) {
+      omSnapshotPurgeResponse.checkAndUpdateDB(getOmMetadataManager(), batchOperation);
+      getOmMetadataManager().getStore().commitBatchOperation(batchOperation);
     }
   }
 
   @Test
   public void testValidateAndUpdateCache() throws Exception {
-    long initialSnapshotPurgeCount = omMetrics.getNumSnapshotPurges();
-    long initialSnapshotPurgeFailCount = omMetrics.getNumSnapshotPurgeFails();
+    long initialSnapshotPurgeCount = getOmMetrics().getNumSnapshotPurges();
+    long initialSnapshotPurgeFailCount = getOmMetrics().getNumSnapshotPurgeFails();
 
     List<String> snapshotDbKeysToPurge = createSnapshots(10);
-    assertFalse(omMetadataManager.getSnapshotInfoTable().isEmpty());
+    assertFalse(getOmMetadataManager().getSnapshotInfoTable().isEmpty());
     OMRequest snapshotPurgeRequest = createPurgeKeysRequest(
         snapshotDbKeysToPurge);
 
     OMSnapshotPurgeRequest omSnapshotPurgeRequest = preExecute(snapshotPurgeRequest);
 
     OMSnapshotPurgeResponse omSnapshotPurgeResponse = (OMSnapshotPurgeResponse)
-        omSnapshotPurgeRequest.validateAndUpdateCache(ozoneManager, 200L);
+        omSnapshotPurgeRequest.validateAndUpdateCache(getOzoneManager(), 200L);
 
     for (String snapshotTableKey: snapshotDbKeysToPurge) {
-      assertNull(omMetadataManager.getSnapshotInfoTable().get(snapshotTableKey));
+      assertNull(getOmMetadataManager().getSnapshotInfoTable().get(snapshotTableKey));
     }
 
-    try (BatchOperation batchOperation = omMetadataManager.getStore().initBatchOperation()) {
-      omSnapshotPurgeResponse.checkAndUpdateDB(omMetadataManager, batchOperation);
-      omMetadataManager.getStore().commitBatchOperation(batchOperation);
+    try (BatchOperation batchOperation = getOmMetadataManager().getStore().initBatchOperation()) {
+      omSnapshotPurgeResponse.checkAndUpdateDB(getOmMetadataManager(), batchOperation);
+      getOmMetadataManager().getStore().commitBatchOperation(batchOperation);
     }
 
     // Check if the entries are deleted.
-    assertTrue(omMetadataManager.getSnapshotInfoTable().isEmpty());
+    assertTrue(getOmMetadataManager().getSnapshotInfoTable().isEmpty());
 
     // Check if all the checkpoints are cleared.
     for (Path checkpoint : checkpointPaths) {
       assertFalse(Files.exists(checkpoint));
     }
-    assertEquals(initialSnapshotPurgeCount + 1, omMetrics.getNumSnapshotPurges());
-    assertEquals(initialSnapshotPurgeFailCount, omMetrics.getNumSnapshotPurgeFails());
+    assertEquals(initialSnapshotPurgeCount + 1, getOmMetrics().getNumSnapshotPurges());
+    assertEquals(initialSnapshotPurgeFailCount, getOmMetrics().getNumSnapshotPurgeFails());
   }
 
   /**
@@ -276,8 +191,8 @@ public class TestOMSnapshotPurgeRequestAndResponse {
    */
   @Test
   public void testValidateAndUpdateCacheFailure() throws Exception {
-    long initialSnapshotPurgeCount = omMetrics.getNumSnapshotPurges();
-    long initialSnapshotPurgeFailCount = omMetrics.getNumSnapshotPurgeFails();
+    long initialSnapshotPurgeCount = getOmMetrics().getNumSnapshotPurges();
+    long initialSnapshotPurgeFailCount = getOmMetrics().getNumSnapshotPurgeFails();
 
     List<String> snapshotDbKeysToPurge = createSnapshots(10);
 
@@ -286,17 +201,17 @@ public class TestOMSnapshotPurgeRequestAndResponse {
 
     when(mockedSnapshotInfoTable.get(anyString())).thenThrow(new IOException("Injected fault error."));
     when(mockedMetadataManager.getSnapshotInfoTable()).thenReturn(mockedSnapshotInfoTable);
-    when(ozoneManager.getMetadataManager()).thenReturn(mockedMetadataManager);
+    when(getOzoneManager().getMetadataManager()).thenReturn(mockedMetadataManager);
 
     OMRequest snapshotPurgeRequest = createPurgeKeysRequest(snapshotDbKeysToPurge);
     OMSnapshotPurgeRequest omSnapshotPurgeRequest = preExecute(snapshotPurgeRequest);
 
     OMSnapshotPurgeResponse omSnapshotPurgeResponse = (OMSnapshotPurgeResponse)
-        omSnapshotPurgeRequest.validateAndUpdateCache(ozoneManager, 200L);
+        omSnapshotPurgeRequest.validateAndUpdateCache(getOzoneManager(), 200L);
 
     assertEquals(INTERNAL_ERROR, omSnapshotPurgeResponse.getOMResponse().getStatus());
-    assertEquals(initialSnapshotPurgeCount, omMetrics.getNumSnapshotPurges());
-    assertEquals(initialSnapshotPurgeFailCount + 1, omMetrics.getNumSnapshotPurgeFails());
+    assertEquals(initialSnapshotPurgeCount, getOmMetrics().getNumSnapshotPurges());
+    assertEquals(initialSnapshotPurgeFailCount + 1, getOmMetrics().getNumSnapshotPurgeFails());
   }
 
   // TODO: clean up: Do we this test after
@@ -309,7 +224,7 @@ public class TestOMSnapshotPurgeRequestAndResponse {
 
     // Before purge, check snapshot chain
     OmMetadataManagerImpl metadataManager =
-        (OmMetadataManagerImpl) omMetadataManager;
+        (OmMetadataManagerImpl) getOmMetadataManager();
     SnapshotChainManager chainManager = metadataManager
         .getSnapshotChainManager();
     SnapshotInfo snapInfo = metadataManager.getSnapshotInfoTable()
@@ -343,8 +258,8 @@ public class TestOMSnapshotPurgeRequestAndResponse {
           snapInfo.getSnapshotId());
     }
 
-    long rowsInTableBeforePurge = omMetadataManager
-        .countRowsInTable(omMetadataManager.getSnapshotInfoTable());
+    long rowsInTableBeforePurge = getOmMetadataManager()
+        .countRowsInTable(getOmMetadataManager().getSnapshotInfoTable());
     // Purge Snapshot of the given index.
     List<String> toPurgeList = Collections.singletonList(snapShotToPurge);
     OMRequest snapshotPurgeRequest = createPurgeKeysRequest(
@@ -367,8 +282,8 @@ public class TestOMSnapshotPurgeRequestAndResponse {
           .getGlobalPreviousSnapshotId(), prevGlobalSnapId);
     }
 
-    assertNotEquals(rowsInTableBeforePurge, omMetadataManager
-        .countRowsInTable(omMetadataManager.getSnapshotInfoTable()));
+    assertNotEquals(rowsInTableBeforePurge, getOmMetadataManager()
+        .countRowsInTable(getOmMetadataManager().getSnapshotInfoTable()));
   }
 
   private static Stream<Arguments> snapshotPurgeCases() {
@@ -422,14 +337,14 @@ public class TestOMSnapshotPurgeRequestAndResponse {
       int toIndex,
       boolean createInBucketOrder) throws Exception {
     SnapshotChainManager chainManager =
-        ((OmMetadataManagerImpl) omMetadataManager).getSnapshotChainManager();
+        ((OmMetadataManagerImpl) getOmMetadataManager()).getSnapshotChainManager();
     int totalKeys = numberOfBuckets * numberOfKeysPerBucket;
 
     List<String> buckets = new ArrayList<>();
     for (int i = 0; i < numberOfBuckets; i++) {
       String bucketNameLocal = "bucket-" + UUID.randomUUID();
-      OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketNameLocal,
-          omMetadataManager);
+      OMRequestTestUtils.addVolumeAndBucketToDB(getVolumeName(), bucketNameLocal,
+          getOmMetadataManager());
       buckets.add(bucketNameLocal);
     }
 
@@ -440,17 +355,17 @@ public class TestOMSnapshotPurgeRequestAndResponse {
         int bucketIndex = createInBucketOrder ? i : j;
         String bucket = buckets.get(bucketIndex % numberOfBuckets);
         String snapshotName = UUID.randomUUID().toString();
-        createSnapshotCheckpoint(volumeName, bucket, snapshotName);
+        createSnapshotCheckpoint(getVolumeName(), bucket, snapshotName);
         String snapshotTableKey =
-            SnapshotInfo.getTableKey(volumeName, bucket, snapshotName);
+            SnapshotInfo.getTableKey(getVolumeName(), bucket, snapshotName);
         SnapshotInfo snapshotInfo =
-            omMetadataManager.getSnapshotInfoTable().get(snapshotTableKey);
+            getOmMetadataManager().getSnapshotInfoTable().get(snapshotTableKey);
         snapshotInfoList.add(snapshotInfo);
       }
     }
 
-    long numberOfSnapshotBeforePurge = omMetadataManager
-        .countRowsInTable(omMetadataManager.getSnapshotInfoTable());
+    long numberOfSnapshotBeforePurge = getOmMetadataManager()
+        .countRowsInTable(getOmMetadataManager().getSnapshotInfoTable());
     assertEquals(totalKeys, numberOfSnapshotBeforePurge);
     assertEquals(totalKeys, chainManager.getGlobalSnapshotChain().size());
     Map<UUID, ByteString> expectedTransactionInfos = new HashMap<>();
@@ -476,7 +391,7 @@ public class TestOMSnapshotPurgeRequestAndResponse {
         expectedTransactionInfos.put(chainManager.nextPathSnapshot(purgeSnapshotInfo.getSnapshotPath(), snapId),
             expectedLastTransactionVal);
       }
-      String purgeSnapshotKey = SnapshotInfo.getTableKey(volumeName,
+      String purgeSnapshotKey = SnapshotInfo.getTableKey(getVolumeName(),
           purgeSnapshotInfo.getBucketName(),
           purgeSnapshotInfo.getName());
       purgeSnapshotKeys.add(purgeSnapshotKey);
@@ -489,17 +404,17 @@ public class TestOMSnapshotPurgeRequestAndResponse {
     for (int i = 0; i < totalKeys; i++) {
       if (i < fromIndex || i > toIndex) {
         SnapshotInfo info = snapshotInfoList.get(i);
-        String snapshotKey = SnapshotInfo.getTableKey(volumeName,
+        String snapshotKey = SnapshotInfo.getTableKey(getVolumeName(),
             info.getBucketName(), info.getName());
         snapshotInfoListAfterPurge.add(
-            omMetadataManager.getSnapshotInfoTable().get(snapshotKey));
+            getOmMetadataManager().getSnapshotInfoTable().get(snapshotKey));
       }
     }
 
     long expectNumberOfSnapshotAfterPurge = totalKeys -
         (toIndex - fromIndex + 1);
-    long actualNumberOfSnapshotAfterPurge = omMetadataManager
-        .countRowsInTable(omMetadataManager.getSnapshotInfoTable());
+    long actualNumberOfSnapshotAfterPurge = getOmMetadataManager()
+        .countRowsInTable(getOmMetadataManager().getSnapshotInfoTable());
     assertEquals(expectNumberOfSnapshotAfterPurge,
         actualNumberOfSnapshotAfterPurge);
     assertEquals(expectNumberOfSnapshotAfterPurge, chainManager
@@ -516,7 +431,7 @@ public class TestOMSnapshotPurgeRequestAndResponse {
       assertEquals(snapshotInfo.getLastTransactionInfo(), expectedTransactionInfos.get(snapshotInfo.getSnapshotId()));
     }
     OmMetadataManagerImpl metadataManager =
-        (OmMetadataManagerImpl) omMetadataManager;
+        (OmMetadataManagerImpl) getOmMetadataManager();
     SnapshotChainManager chainManager = metadataManager
         .getSnapshotChainManager();
 
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotRenameRequest.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotRenameRequest.java
index ab2bac1bd0..a746597288 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotRenameRequest.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotRenameRequest.java
@@ -17,17 +17,8 @@
 package org.apache.hadoop.ozone.om.request.snapshot;
 
 import org.apache.hadoop.hdds.client.RatisReplicationConfig;
-import org.apache.hadoop.hdds.conf.OzoneConfiguration;
-import org.apache.hadoop.hdds.utils.db.BatchOperation;
 import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
-import org.apache.hadoop.ozone.OzoneConfigKeys;
-import org.apache.hadoop.ozone.audit.AuditLogger;
-import org.apache.hadoop.ozone.audit.AuditMessage;
-import org.apache.hadoop.ozone.om.OMConfigKeys;
-import org.apache.hadoop.ozone.om.OMMetrics;
-import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
-import org.apache.hadoop.ozone.om.OmSnapshotManager;
 import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
@@ -35,17 +26,14 @@ import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
 import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
 import org.apache.hadoop.ozone.om.response.OMClientResponse;
-import org.apache.hadoop.ozone.om.upgrade.OMLayoutVersionManager;
+import org.apache.hadoop.ozone.om.snapshot.TestSnapshotRequestAndResponse;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
 import org.apache.hadoop.util.Time;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
-import java.io.File;
 import java.util.UUID;
 
 import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.THREE;
@@ -62,75 +50,19 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.framework;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 /**
  * Tests OMSnapshotRenameRequest class, which handles RenameSnapshot request.
  */
-public class TestOMSnapshotRenameRequest {
-
-  @TempDir
-  private File anotherTempDir;
-
-  private OzoneManager ozoneManager;
-  private OMMetrics omMetrics;
-  private OmMetadataManagerImpl omMetadataManager;
-  private BatchOperation batchOperation;
-
-  private String volumeName;
-  private String bucketName;
+public class TestOMSnapshotRenameRequest extends TestSnapshotRequestAndResponse {
   private String snapshotName1;
   private String snapshotName2;
 
   @BeforeEach
   public void setup() throws Exception {
-    ozoneManager = mock(OzoneManager.class);
-    omMetrics = OMMetrics.create();
-    OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
-    ozoneConfiguration.set(OMConfigKeys.OZONE_OM_DB_DIRS,
-        anotherTempDir.getAbsolutePath());
-    ozoneConfiguration.set(OzoneConfigKeys.OZONE_METADATA_DIRS,
-        anotherTempDir.getAbsolutePath());
-    omMetadataManager = new OmMetadataManagerImpl(ozoneConfiguration,
-        ozoneManager);
-    when(ozoneManager.getMetrics()).thenReturn(omMetrics);
-    when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
-    when(ozoneManager.isRatisEnabled()).thenReturn(true);
-    when(ozoneManager.isFilesystemSnapshotEnabled()).thenReturn(true);
-    when(ozoneManager.isAdmin(any())).thenReturn(false);
-    when(ozoneManager.isOwner(any(), any())).thenReturn(false);
-    when(ozoneManager.getBucketOwner(any(), any(),
-        any(), any())).thenReturn("dummyBucketOwner");
-    OMLayoutVersionManager lvm = mock(OMLayoutVersionManager.class);
-    when(lvm.isAllowed(anyString())).thenReturn(true);
-    when(ozoneManager.getVersionManager()).thenReturn(lvm);
-    AuditLogger auditLogger = mock(AuditLogger.class);
-    when(ozoneManager.getAuditLogger()).thenReturn(auditLogger);
-    doNothing().when(auditLogger).logWrite(any(AuditMessage.class));
-    batchOperation = omMetadataManager.getStore().initBatchOperation();
-    when(ozoneManager.getConfiguration()).thenReturn(ozoneConfiguration);
-    OmSnapshotManager omSnapshotManager = new OmSnapshotManager(ozoneManager);
-    when(ozoneManager.getOmSnapshotManager()).thenReturn(omSnapshotManager);
-
-    volumeName = UUID.randomUUID().toString();
-    bucketName = UUID.randomUUID().toString();
     snapshotName1 = UUID.randomUUID().toString();
     snapshotName2 = UUID.randomUUID().toString();
-    OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
-        omMetadataManager);
-  }
-
-  @AfterEach
-  public void stop() {
-    omMetrics.unRegister();
-    framework().clearInlineMocks();
-    if (batchOperation != null) {
-      batchOperation.close();
-    }
   }
 
   @ValueSource(strings = {
@@ -143,11 +75,11 @@ public class TestOMSnapshotRenameRequest {
   })
   @ParameterizedTest
   public void testPreExecute(String toSnapshotName) throws Exception {
-    when(ozoneManager.isOwner(any(), any())).thenReturn(true);
+    when(getOzoneManager().isOwner(any(), any())).thenReturn(true);
 
     String currentSnapshotName = "current";
-    OzoneManagerProtocolProtos.OMRequest omRequest = renameSnapshotRequest(volumeName,
-        bucketName, currentSnapshotName, toSnapshotName);
+    OzoneManagerProtocolProtos.OMRequest omRequest = renameSnapshotRequest(getVolumeName(),
+        getBucketName(), currentSnapshotName, toSnapshotName);
     doPreExecute(omRequest);
   }
 
@@ -167,10 +99,10 @@ public class TestOMSnapshotRenameRequest {
   })
   @ParameterizedTest
   public void testPreExecuteFailure(String toSnapshotName) {
-    when(ozoneManager.isOwner(any(), any())).thenReturn(true);
+    when(getOzoneManager().isOwner(any(), any())).thenReturn(true);
     String currentSnapshotName = "current";
-    OzoneManagerProtocolProtos.OMRequest omRequest = renameSnapshotRequest(volumeName,
-        bucketName, currentSnapshotName, toSnapshotName);
+    OzoneManagerProtocolProtos.OMRequest omRequest = renameSnapshotRequest(getVolumeName(),
+        getBucketName(), currentSnapshotName, toSnapshotName);
     OMException omException =
         assertThrows(OMException.class, () -> doPreExecute(omRequest));
     assertTrue(omException.getMessage().contains("Invalid snapshot name: " + toSnapshotName));
@@ -179,8 +111,8 @@ public class TestOMSnapshotRenameRequest {
   @Test
   public void testPreExecuteBadOwner() {
     // Owner is not set for the request.
-    OzoneManagerProtocolProtos.OMRequest omRequest = renameSnapshotRequest(volumeName,
-        bucketName, snapshotName1, snapshotName2);
+    OzoneManagerProtocolProtos.OMRequest omRequest = renameSnapshotRequest(getVolumeName(),
+        getBucketName(), snapshotName1, snapshotName2);
 
     OMException omException = assertThrows(OMException.class,
         () -> doPreExecute(omRequest));
@@ -190,39 +122,39 @@ public class TestOMSnapshotRenameRequest {
 
   @Test
   public void testValidateAndUpdateCache() throws Exception {
-    when(ozoneManager.isAdmin(any())).thenReturn(true);
-    OzoneManagerProtocolProtos.OMRequest omRequest = renameSnapshotRequest(volumeName,
-        bucketName, snapshotName1, snapshotName2);
+    when(getOzoneManager().isAdmin(any())).thenReturn(true);
+    OzoneManagerProtocolProtos.OMRequest omRequest = renameSnapshotRequest(getVolumeName(),
+        getBucketName(), snapshotName1, snapshotName2);
     OMSnapshotRenameRequest omSnapshotRenameRequest = doPreExecute(omRequest);
-    String key = getTableKey(volumeName, bucketName, snapshotName1);
-    String bucketKey = omMetadataManager.getBucketKey(volumeName, bucketName);
+    String key = getTableKey(getVolumeName(), getBucketName(), snapshotName1);
+    String bucketKey = getOmMetadataManager().getBucketKey(getVolumeName(), getBucketName());
 
     // Add a 1000-byte key to the bucket
     OmKeyInfo key1 = addKey("key-testValidateAndUpdateCache", 12345L);
     addKeyToTable(key1);
 
-    OmBucketInfo omBucketInfo = omMetadataManager.getBucketTable().get(
+    OmBucketInfo omBucketInfo = getOmMetadataManager().getBucketTable().get(
         bucketKey);
     long bucketDataSize = key1.getDataSize();
     long bucketUsedBytes = omBucketInfo.getUsedBytes();
     assertEquals(key1.getReplicatedSize(), bucketUsedBytes);
 
     // Value in cache should be null as of now.
-    assertNull(omMetadataManager.getSnapshotInfoTable().get(key));
+    assertNull(getOmMetadataManager().getSnapshotInfoTable().get(key));
 
     // Add key to cache.
-    SnapshotInfo snapshotInfo = SnapshotInfo.newInstance(volumeName, bucketName,
+    SnapshotInfo snapshotInfo = SnapshotInfo.newInstance(getVolumeName(), getBucketName(),
         snapshotName1, UUID.randomUUID(), Time.now());
     snapshotInfo.setReferencedSize(1000L);
     snapshotInfo.setReferencedReplicatedSize(3 * 1000L);
     assertEquals(SNAPSHOT_ACTIVE, snapshotInfo.getSnapshotStatus());
-    omMetadataManager.getSnapshotInfoTable().addCacheEntry(
+    getOmMetadataManager().getSnapshotInfoTable().addCacheEntry(
         new CacheKey<>(key),
         CacheValue.get(1L, snapshotInfo));
 
     // Run validateAndUpdateCache.
     OMClientResponse omClientResponse =
-        omSnapshotRenameRequest.validateAndUpdateCache(ozoneManager, 2L);
+        omSnapshotRenameRequest.validateAndUpdateCache(getOzoneManager(), 2L);
 
     assertNotNull(omClientResponse.getOMResponse());
 
@@ -244,56 +176,56 @@ public class TestOMSnapshotRenameRequest {
 
     SnapshotInfo snapshotInfoOldProto = getFromProtobuf(snapshotInfoProto);
 
-    String key2 = getTableKey(volumeName, bucketName, snapshotName2);
+    String key2 = getTableKey(getVolumeName(), getBucketName(), snapshotName2);
 
     // Get value from cache
     SnapshotInfo snapshotInfoNewInCache =
-        omMetadataManager.getSnapshotInfoTable().get(key2);
+        getOmMetadataManager().getSnapshotInfoTable().get(key2);
     assertNotNull(snapshotInfoNewInCache);
     assertEquals(snapshotInfoOldProto, snapshotInfoNewInCache);
     assertEquals(snapshotInfo.getSnapshotId(), snapshotInfoNewInCache.getSnapshotId());
 
     SnapshotInfo snapshotInfoOldInCache =
-        omMetadataManager.getSnapshotInfoTable().get(key);
+        getOmMetadataManager().getSnapshotInfoTable().get(key);
     assertNull(snapshotInfoOldInCache);
   }
 
   @Test
   public void testEntryExists() throws Exception {
-    when(ozoneManager.isAdmin(any())).thenReturn(true);
+    when(getOzoneManager().isAdmin(any())).thenReturn(true);
 
-    String keyNameOld = getTableKey(volumeName, bucketName, snapshotName1);
-    String keyNameNew = getTableKey(volumeName, bucketName, snapshotName2);
+    String keyNameOld = getTableKey(getVolumeName(), getBucketName(), snapshotName1);
+    String keyNameNew = getTableKey(getVolumeName(), getBucketName(), snapshotName2);
 
-    assertNull(omMetadataManager.getSnapshotInfoTable().get(keyNameOld));
-    assertNull(omMetadataManager.getSnapshotInfoTable().get(keyNameNew));
+    assertNull(getOmMetadataManager().getSnapshotInfoTable().get(keyNameOld));
+    assertNull(getOmMetadataManager().getSnapshotInfoTable().get(keyNameNew));
 
     // First make sure we have two snapshots.
     OzoneManagerProtocolProtos.OMRequest createOmRequest =
-        createSnapshotRequest(volumeName, bucketName, snapshotName1);
+        createSnapshotRequest(getVolumeName(), getBucketName(), snapshotName1);
     OMSnapshotCreateRequest omSnapshotCreateRequest =
-        TestOMSnapshotCreateRequest.doPreExecute(createOmRequest, ozoneManager);
-    omSnapshotCreateRequest.validateAndUpdateCache(ozoneManager, 1);
+        TestOMSnapshotCreateRequest.doPreExecute(createOmRequest, getOzoneManager());
+    omSnapshotCreateRequest.validateAndUpdateCache(getOzoneManager(), 1);
 
     createOmRequest =
-        createSnapshotRequest(volumeName, bucketName, snapshotName2);
+        createSnapshotRequest(getVolumeName(), getBucketName(), snapshotName2);
     omSnapshotCreateRequest =
-        TestOMSnapshotCreateRequest.doPreExecute(createOmRequest, ozoneManager);
-    omSnapshotCreateRequest.validateAndUpdateCache(ozoneManager, 2);
+        TestOMSnapshotCreateRequest.doPreExecute(createOmRequest, getOzoneManager());
+    omSnapshotCreateRequest.validateAndUpdateCache(getOzoneManager(), 2);
 
-    assertNotNull(omMetadataManager.getSnapshotInfoTable().get(keyNameOld));
-    assertNotNull(omMetadataManager.getSnapshotInfoTable().get(keyNameNew));
+    assertNotNull(getOmMetadataManager().getSnapshotInfoTable().get(keyNameOld));
+    assertNotNull(getOmMetadataManager().getSnapshotInfoTable().get(keyNameNew));
 
     // Now try renaming and get an error.
     OzoneManagerProtocolProtos.OMRequest omRequest =
-        renameSnapshotRequest(volumeName, bucketName, snapshotName1, snapshotName2);
+        renameSnapshotRequest(getVolumeName(), getBucketName(), snapshotName1, snapshotName2);
     OMSnapshotRenameRequest omSnapshotRenameRequest = doPreExecute(omRequest);
 
     OMClientResponse omClientResponse =
-        omSnapshotRenameRequest.validateAndUpdateCache(ozoneManager, 3);
+        omSnapshotRenameRequest.validateAndUpdateCache(getOzoneManager(), 3);
 
-    assertNotNull(omMetadataManager.getSnapshotInfoTable().get(keyNameOld));
-    assertNotNull(omMetadataManager.getSnapshotInfoTable().get(keyNameNew));
+    assertNotNull(getOmMetadataManager().getSnapshotInfoTable().get(keyNameOld));
+    assertNotNull(getOmMetadataManager().getSnapshotInfoTable().get(keyNameNew));
 
     OzoneManagerProtocolProtos.OMResponse omResponse = omClientResponse.getOMResponse();
     assertNotNull(omResponse.getRenameSnapshotResponse());
@@ -303,24 +235,24 @@ public class TestOMSnapshotRenameRequest {
 
   @Test
   public void testEntryNotFound() throws Exception {
-    when(ozoneManager.isAdmin(any())).thenReturn(true);
+    when(getOzoneManager().isAdmin(any())).thenReturn(true);
 
-    String keyNameOld = getTableKey(volumeName, bucketName, snapshotName1);
-    String keyNameNew = getTableKey(volumeName, bucketName, snapshotName2);
+    String keyNameOld = getTableKey(getVolumeName(), getBucketName(), snapshotName1);
+    String keyNameNew = getTableKey(getVolumeName(), getBucketName(), snapshotName2);
 
-    assertNull(omMetadataManager.getSnapshotInfoTable().get(keyNameOld));
-    assertNull(omMetadataManager.getSnapshotInfoTable().get(keyNameNew));
+    assertNull(getOmMetadataManager().getSnapshotInfoTable().get(keyNameOld));
+    assertNull(getOmMetadataManager().getSnapshotInfoTable().get(keyNameNew));
 
     // Now try renaming and get an error.
     OzoneManagerProtocolProtos.OMRequest omRequest =
-        renameSnapshotRequest(volumeName, bucketName, snapshotName1, snapshotName2);
+        renameSnapshotRequest(getVolumeName(), getBucketName(), snapshotName1, snapshotName2);
     OMSnapshotRenameRequest omSnapshotRenameRequest = doPreExecute(omRequest);
 
     OMClientResponse omClientResponse =
-        omSnapshotRenameRequest.validateAndUpdateCache(ozoneManager, 3);
+        omSnapshotRenameRequest.validateAndUpdateCache(getOzoneManager(), 3);
 
-    assertNull(omMetadataManager.getSnapshotInfoTable().get(keyNameOld));
-    assertNull(omMetadataManager.getSnapshotInfoTable().get(keyNameNew));
+    assertNull(getOmMetadataManager().getSnapshotInfoTable().get(keyNameOld));
+    assertNull(getOmMetadataManager().getSnapshotInfoTable().get(keyNameNew));
 
     OzoneManagerProtocolProtos.OMResponse omResponse = omClientResponse.getOMResponse();
     assertNotNull(omResponse.getRenameSnapshotResponse());
@@ -330,7 +262,7 @@ public class TestOMSnapshotRenameRequest {
 
   private OMSnapshotRenameRequest doPreExecute(
       OzoneManagerProtocolProtos.OMRequest originalRequest) throws Exception {
-    return doPreExecute(originalRequest, ozoneManager);
+    return doPreExecute(originalRequest, getOzoneManager());
   }
 
   public static OMSnapshotRenameRequest doPreExecute(
@@ -344,15 +276,15 @@ public class TestOMSnapshotRenameRequest {
   }
 
   private OmKeyInfo addKey(String keyName, long objectId) {
-    return OMRequestTestUtils.createOmKeyInfo(volumeName, bucketName, keyName,
+    return OMRequestTestUtils.createOmKeyInfo(getVolumeName(), getBucketName(), keyName,
             RatisReplicationConfig.getInstance(THREE)).setObjectID(objectId)
         .build();
   }
 
   protected String addKeyToTable(OmKeyInfo keyInfo) throws Exception {
     OMRequestTestUtils.addKeyToTable(false, true, keyInfo, 0, 0L,
-        omMetadataManager);
-    return omMetadataManager.getOzoneKey(keyInfo.getVolumeName(),
+        getOmMetadataManager());
+    return getOmMetadataManager().getOzoneKey(keyInfo.getVolumeName(),
         keyInfo.getBucketName(), keyInfo.getKeyName());
   }
 
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotSetPropertyRequestAndResponse.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotSetPropertyRequestAndResponse.java
index b5bfc2714b..380922f9e2 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotSetPropertyRequestAndResponse.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotSetPropertyRequestAndResponse.java
@@ -18,32 +18,23 @@
  */
 package org.apache.hadoop.ozone.om.request.snapshot;
 
-import org.apache.hadoop.hdds.conf.OzoneConfiguration;
-import org.apache.hadoop.hdds.utils.db.BatchOperation;
 import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.hdds.utils.db.TableIterator;
 import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
-import org.apache.hadoop.ozone.OzoneConfigKeys;
-import org.apache.hadoop.ozone.om.OMConfigKeys;
-import org.apache.hadoop.ozone.om.OMMetadataManager;
-import org.apache.hadoop.ozone.om.OMMetrics;
 import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
-import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
 import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
 
 import org.apache.hadoop.ozone.om.response.snapshot.OMSnapshotSetPropertyResponse;
-import org.apache.hadoop.ozone.om.upgrade.OMLayoutVersionManager;
+import org.apache.hadoop.ozone.om.snapshot.TestSnapshotRequestAndResponse;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SnapshotSize;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetSnapshotPropertyRequest;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.io.TempDir;
 
-import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -60,37 +51,13 @@ import static org.mockito.Mockito.when;
  * Tests TestOMSnapshotSetPropertyRequest
  * TestOMSnapshotSetPropertyResponse class.
  */
-public class TestOMSnapshotSetPropertyRequestAndResponse {
-  private BatchOperation batchOperation;
-  private OzoneManager ozoneManager;
-  private OMMetadataManager omMetadataManager;
-  private OMMetrics omMetrics;
-  private String volumeName;
-  private String bucketName;
+public class TestOMSnapshotSetPropertyRequestAndResponse extends TestSnapshotRequestAndResponse {
   private String snapName;
   private long exclusiveSize;
   private long exclusiveSizeAfterRepl;
 
   @BeforeEach
-  void setup(@TempDir File testDir) throws Exception {
-    omMetrics = OMMetrics.create();
-    ozoneManager = mock(OzoneManager.class);
-    OMLayoutVersionManager lvm = mock(OMLayoutVersionManager.class);
-    when(lvm.isAllowed(anyString())).thenReturn(true);
-    when(ozoneManager.getVersionManager()).thenReturn(lvm);
-    when(ozoneManager.isRatisEnabled()).thenReturn(true);
-    OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
-    ozoneConfiguration.set(OMConfigKeys.OZONE_OM_DB_DIRS,
-        testDir.getAbsolutePath());
-    ozoneConfiguration.set(OzoneConfigKeys.OZONE_METADATA_DIRS,
-        testDir.getAbsolutePath());
-    omMetadataManager = new OmMetadataManagerImpl(ozoneConfiguration,
-        ozoneManager);
-    when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
-    when(ozoneManager.getMetrics()).thenReturn(omMetrics);
-
-    volumeName = UUID.randomUUID().toString();
-    bucketName = UUID.randomUUID().toString();
+  void setup() {
     snapName = UUID.randomUUID().toString();
     exclusiveSize = 2000L;
     exclusiveSizeAfterRepl = 6000L;
@@ -98,11 +65,11 @@ public class TestOMSnapshotSetPropertyRequestAndResponse {
 
   @Test
   public void testValidateAndUpdateCache() throws IOException {
-    long initialSnapshotSetPropertyCount = omMetrics.getNumSnapshotSetProperties();
-    long initialSnapshotSetPropertyFailCount = omMetrics.getNumSnapshotSetPropertyFails();
+    long initialSnapshotSetPropertyCount = getOmMetrics().getNumSnapshotSetProperties();
+    long initialSnapshotSetPropertyFailCount = getOmMetrics().getNumSnapshotSetPropertyFails();
 
     createSnapshotDataForTest();
-    assertFalse(omMetadataManager.getSnapshotInfoTable().isEmpty());
+    assertFalse(getOmMetadataManager().getSnapshotInfoTable().isEmpty());
     List<OMRequest> snapshotUpdateSizeRequests =
         createSnapshotUpdateSizeRequest();
 
@@ -111,28 +78,27 @@ public class TestOMSnapshotSetPropertyRequestAndResponse {
       OMSnapshotSetPropertyRequest omSnapshotSetPropertyRequest = new
           OMSnapshotSetPropertyRequest(request);
       OMRequest modifiedOmRequest = omSnapshotSetPropertyRequest
-          .preExecute(ozoneManager);
+          .preExecute(getOzoneManager());
       omSnapshotSetPropertyRequest = new
           OMSnapshotSetPropertyRequest(modifiedOmRequest);
 
       // Validate and Update Cache
       OMSnapshotSetPropertyResponse omSnapshotSetPropertyResponse =
           (OMSnapshotSetPropertyResponse) omSnapshotSetPropertyRequest
-              .validateAndUpdateCache(ozoneManager, 200L);
+              .validateAndUpdateCache(getOzoneManager(), 200L);
 
       // Commit to DB.
-      batchOperation = omMetadataManager.getStore().initBatchOperation();
-      omSnapshotSetPropertyResponse.checkAndUpdateDB(omMetadataManager,
-          batchOperation);
-      omMetadataManager.getStore().commitBatchOperation(batchOperation);
+      omSnapshotSetPropertyResponse.checkAndUpdateDB(getOmMetadataManager(),
+          getBatchOperation());
+      getOmMetadataManager().getStore().commitBatchOperation(getBatchOperation());
     }
 
     assertEquals(initialSnapshotSetPropertyCount + snapshotUpdateSizeRequests.size(),
-        omMetrics.getNumSnapshotSetProperties());
-    assertEquals(initialSnapshotSetPropertyFailCount, omMetrics.getNumSnapshotSetPropertyFails());
+        getOmMetrics().getNumSnapshotSetProperties());
+    assertEquals(initialSnapshotSetPropertyFailCount, getOmMetrics().getNumSnapshotSetPropertyFails());
     // Check if the exclusive size is set.
     try (TableIterator<String, ? extends Table.KeyValue<String, SnapshotInfo>>
-             iterator = omMetadataManager.getSnapshotInfoTable().iterator()) {
+             iterator = getOmMetadataManager().getSnapshotInfoTable().iterator()) {
       while (iterator.hasNext()) {
         Table.KeyValue<String, SnapshotInfo> snapshotEntry = iterator.next();
         assertCacheValues(snapshotEntry.getKey());
@@ -149,11 +115,11 @@ public class TestOMSnapshotSetPropertyRequestAndResponse {
    */
   @Test
   public void testValidateAndUpdateCacheFailure() throws IOException {
-    long initialSnapshotSetPropertyCount = omMetrics.getNumSnapshotSetProperties();
-    long initialSnapshotSetPropertyFailCount = omMetrics.getNumSnapshotSetPropertyFails();
+    long initialSnapshotSetPropertyCount = getOmMetrics().getNumSnapshotSetProperties();
+    long initialSnapshotSetPropertyFailCount = getOmMetrics().getNumSnapshotSetPropertyFails();
 
     createSnapshotDataForTest();
-    assertFalse(omMetadataManager.getSnapshotInfoTable().isEmpty());
+    assertFalse(getOmMetadataManager().getSnapshotInfoTable().isEmpty());
     List<OMRequest> snapshotUpdateSizeRequests = createSnapshotUpdateSizeRequest();
 
     OmMetadataManagerImpl mockedMetadataManager = mock(OmMetadataManagerImpl.class);
@@ -161,27 +127,27 @@ public class TestOMSnapshotSetPropertyRequestAndResponse {
 
     when(mockedSnapshotInfoTable.get(anyString())).thenThrow(new IOException("Injected fault error."));
     when(mockedMetadataManager.getSnapshotInfoTable()).thenReturn(mockedSnapshotInfoTable);
-    when(ozoneManager.getMetadataManager()).thenReturn(mockedMetadataManager);
+    when(getOzoneManager().getMetadataManager()).thenReturn(mockedMetadataManager);
 
     for (OMRequest omRequest: snapshotUpdateSizeRequests) {
       OMSnapshotSetPropertyRequest omSnapshotSetPropertyRequest = new OMSnapshotSetPropertyRequest(omRequest);
-      OMRequest modifiedOmRequest = omSnapshotSetPropertyRequest.preExecute(ozoneManager);
+      OMRequest modifiedOmRequest = omSnapshotSetPropertyRequest.preExecute(getOzoneManager());
       omSnapshotSetPropertyRequest = new OMSnapshotSetPropertyRequest(modifiedOmRequest);
 
       // Validate and Update Cache
       OMSnapshotSetPropertyResponse omSnapshotSetPropertyResponse = (OMSnapshotSetPropertyResponse)
-          omSnapshotSetPropertyRequest.validateAndUpdateCache(ozoneManager, 200L);
+          omSnapshotSetPropertyRequest.validateAndUpdateCache(getOzoneManager(), 200L);
 
       assertEquals(INTERNAL_ERROR, omSnapshotSetPropertyResponse.getOMResponse().getStatus());
     }
 
-    assertEquals(initialSnapshotSetPropertyCount, omMetrics.getNumSnapshotSetProperties());
+    assertEquals(initialSnapshotSetPropertyCount, getOmMetrics().getNumSnapshotSetProperties());
     assertEquals(initialSnapshotSetPropertyFailCount + snapshotUpdateSizeRequests.size(),
-        omMetrics.getNumSnapshotSetPropertyFails());
+        getOmMetrics().getNumSnapshotSetPropertyFails());
   }
 
   private void assertCacheValues(String dbKey) {
-    CacheValue<SnapshotInfo> cacheValue = omMetadataManager
+    CacheValue<SnapshotInfo> cacheValue = getOmMetadataManager()
         .getSnapshotInfoTable()
         .getCacheValue(new CacheKey<>(dbKey));
     assertEquals(exclusiveSize, cacheValue.getCacheValue().getExclusiveSize());
@@ -193,7 +159,7 @@ public class TestOMSnapshotSetPropertyRequestAndResponse {
       throws IOException {
     List<OMRequest> omRequests = new ArrayList<>();
     try (TableIterator<String, ? extends Table.KeyValue<String, SnapshotInfo>>
-             iterator = omMetadataManager.getSnapshotInfoTable().iterator()) {
+             iterator = getOmMetadataManager().getSnapshotInfoTable().iterator()) {
       while (iterator.hasNext()) {
         String snapDbKey = iterator.next().getKey();
         SnapshotSize snapshotSize = SnapshotSize.newBuilder()
@@ -220,8 +186,8 @@ public class TestOMSnapshotSetPropertyRequestAndResponse {
   private void createSnapshotDataForTest() throws IOException {
     // Create 10 Snapshots
     for (int i = 0; i < 10; i++) {
-      OMRequestTestUtils.addSnapshotToTableCache(volumeName, bucketName,
-          snapName + i, omMetadataManager);
+      OMRequestTestUtils.addSnapshotToTableCache(getVolumeName(), getBucketName(),
+          snapName + i, getOmMetadataManager());
     }
   }
 }
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/snapshot/TestOMSnapshotMoveTableKeysResponse.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/snapshot/TestOMSnapshotMoveTableKeysResponse.java
new file mode 100644
index 0000000000..d2e2d94ec7
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/snapshot/TestOMSnapshotMoveTableKeysResponse.java
@@ -0,0 +1,199 @@
+/*
+ * 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.om.response.snapshot;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.BatchOperation;
+import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.ozone.ClientVersion;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OmSnapshot;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
+import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted;
+import org.apache.hadoop.ozone.om.snapshot.SnapshotUtils;
+import org.apache.hadoop.ozone.om.snapshot.TestSnapshotRequestAndResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.LongStream;
+
+/**
+ * Test class to test OMSnapshotMoveTableKeysResponse.
+ */
+public class TestOMSnapshotMoveTableKeysResponse extends TestSnapshotRequestAndResponse {
+
+  private String snapshotName1;
+  private String snapshotName2;
+  private SnapshotInfo snapshotInfo1;
+  private SnapshotInfo snapshotInfo2;
+
+  @BeforeEach
+  public void setup() throws Exception {
+    snapshotName1 = UUID.randomUUID().toString();
+    snapshotName2 = UUID.randomUUID().toString();
+  }
+
+  public TestOMSnapshotMoveTableKeysResponse() {
+    super(true);
+  }
+
+  private void createSnapshots(boolean createSecondSnapshot) throws Exception {
+    addDataToTable(getOmMetadataManager().getSnapshotRenamedTable(), getRenameKeys(getVolumeName(), getBucketName(), 0,
+        10,  snapshotName1));
+    addDataToTable(getOmMetadataManager().getDeletedTable(), getDeletedKeys(getVolumeName(), getBucketName(), 0,
+        10, 10, 0).stream()
+        .map(pair -> Pair.of(pair.getKey(), new RepeatedOmKeyInfo(pair.getRight())))
+        .collect(Collectors.toList()));
+    addDataToTable(getOmMetadataManager().getDeletedDirTable(),
+        getDeletedDirKeys(getVolumeName(), getBucketName(), 0, 10, 1).stream()
+        .map(pair -> Pair.of(pair.getKey(), pair.getRight().get(0))).collect(Collectors.toList()));
+    createSnapshotCheckpoint(getVolumeName(), getBucketName(), snapshotName1);
+    snapshotInfo1 = SnapshotUtils.getSnapshotInfo(getOzoneManager(), getVolumeName(), getBucketName(), snapshotName1);
+    addDataToTable(getOmMetadataManager().getSnapshotRenamedTable(), getRenameKeys(getVolumeName(), getBucketName(), 5,
+        15,  snapshotName2));
+    addDataToTable(getOmMetadataManager().getDeletedTable(), getDeletedKeys(getVolumeName(), getBucketName(), 5,
+        8, 10, 10).stream()
+        .map(pair -> Pair.of(pair.getKey(), new RepeatedOmKeyInfo(pair.getRight())))
+        .collect(Collectors.toList()));
+    addDataToTable(getOmMetadataManager().getDeletedTable(), getDeletedKeys(getVolumeName(), getBucketName(), 8,
+        15, 10, 0).stream()
+        .map(pair -> Pair.of(pair.getKey(), new RepeatedOmKeyInfo(pair.getRight())))
+        .collect(Collectors.toList()));
+    addDataToTable(getOmMetadataManager().getDeletedDirTable(),
+        getDeletedDirKeys(getVolumeName(), getBucketName(), 5, 15, 1).stream()
+        .map(pair -> Pair.of(pair.getKey(), pair.getRight().get(0))).collect(Collectors.toList()));
+    if (createSecondSnapshot) {
+      createSnapshotCheckpoint(getVolumeName(), getBucketName(), snapshotName2);
+      snapshotInfo2 = SnapshotUtils.getSnapshotInfo(getOzoneManager(), getVolumeName(), getBucketName(), snapshotName2);
+    }
+  }
+
+  private <V> void addDataToTable(Table<String, V> table, List<Pair<String, V>> vals) throws IOException {
+    for (Pair<String, V> pair : vals) {
+      table.put(pair.getKey(), pair.getValue());
+    }
+  }
+
+  @ParameterizedTest
+  @ValueSource(booleans = {true, false})
+  public void testMoveTableKeysToNextSnapshot(boolean nextSnapshotExists) throws Exception {
+    createSnapshots(nextSnapshotExists);
+
+    try (ReferenceCounted<OmSnapshot> snapshot1 = getOmSnapshotManager().getSnapshot(getVolumeName(), getBucketName(),
+        snapshotName1);
+         ReferenceCounted<OmSnapshot> snapshot2 = nextSnapshotExists ? getOmSnapshotManager().getSnapshot(
+             getVolumeName(), getBucketName(), snapshotName2) : null) {
+      OmSnapshot snapshot = snapshot1.get();
+      List<OzoneManagerProtocolProtos.SnapshotMoveKeyInfos> deletedTable = new ArrayList<>();
+      List<OzoneManagerProtocolProtos.SnapshotMoveKeyInfos> deletedDirTable = new ArrayList<>();
+      List<HddsProtos.KeyValue> renamedTable = new ArrayList<>();
+      Map<String, String> renameEntries = new HashMap<>();
+      snapshot.getMetadataManager().getDeletedTable().iterator()
+          .forEachRemaining(entry -> {
+            try {
+              deletedTable.add(OzoneManagerProtocolProtos.SnapshotMoveKeyInfos.newBuilder().setKey(entry.getKey())
+                  .addAllKeyInfos(entry.getValue().getOmKeyInfoList().stream().map(omKeyInfo -> omKeyInfo.getProtobuf(
+                      ClientVersion.CURRENT_VERSION)).collect(Collectors.toList())).build());
+            } catch (IOException e) {
+              throw new RuntimeException(e);
+            }
+          });
+
+      snapshot.getMetadataManager().getDeletedDirTable().iterator()
+          .forEachRemaining(entry -> {
+            try {
+              deletedDirTable.add(OzoneManagerProtocolProtos.SnapshotMoveKeyInfos.newBuilder().setKey(entry.getKey())
+                  .addKeyInfos(entry.getValue().getProtobuf(ClientVersion.CURRENT_VERSION)).build());
+            } catch (IOException e) {
+              throw new RuntimeException(e);
+            }
+          });
+      snapshot.getMetadataManager().getSnapshotRenamedTable().iterator().forEachRemaining(entry -> {
+        try {
+          renamedTable.add(HddsProtos.KeyValue.newBuilder().setKey(entry.getKey()).setValue(entry.getValue()).build());
+          renameEntries.put(entry.getKey(), entry.getValue());
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      });
+      OMSnapshotMoveTableKeysResponse response = new OMSnapshotMoveTableKeysResponse(
+          OzoneManagerProtocolProtos.OMResponse.newBuilder().setStatus(OzoneManagerProtocolProtos.Status.OK)
+              .setCmdType(OzoneManagerProtocolProtos.Type.SnapshotMoveTableKeys).build(),
+          snapshotInfo1, nextSnapshotExists ? snapshotInfo2 : null, deletedTable, deletedDirTable, renamedTable);
+      try (BatchOperation batchOperation = getOmMetadataManager().getStore().initBatchOperation()) {
+        response.addToDBBatch(getOmMetadataManager(), batchOperation);
+        getOmMetadataManager().getStore().commitBatchOperation(batchOperation);
+      }
+      Assertions.assertTrue(snapshot.getMetadataManager().getDeletedTable().isEmpty());
+      Assertions.assertTrue(snapshot.getMetadataManager().getDeletedDirTable().isEmpty());
+      Assertions.assertTrue(snapshot.getMetadataManager().getSnapshotRenamedTable().isEmpty());
+      OMMetadataManager nextMetadataManager =
+          nextSnapshotExists ? snapshot2.get().getMetadataManager() : getOmMetadataManager();
+      AtomicInteger count = new AtomicInteger();
+      nextMetadataManager.getDeletedTable().iterator().forEachRemaining(entry -> {
+        count.getAndIncrement();
+        try {
+          int maxCount = count.get() >= 6 && count.get() <= 8 ? 20 : 10;
+          Assertions.assertEquals(maxCount, entry.getValue().getOmKeyInfoList().size());
+          List<Long> versions = entry.getValue().getOmKeyInfoList().stream().map(OmKeyInfo::getKeyLocationVersions)
+              .map(omKeyInfo -> omKeyInfo.get(0).getVersion()).collect(Collectors.toList());
+          List<Long> expectedVersions = new ArrayList<>();
+          if (maxCount == 20) {
+            expectedVersions.addAll(LongStream.range(10, 20).boxed().collect(Collectors.toList()));
+          }
+          expectedVersions.addAll(LongStream.range(0, 10).boxed().collect(Collectors.toList()));
+          Assertions.assertEquals(expectedVersions, versions);
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      });
+      Assertions.assertEquals(15, count.get());
+      count.set(0);
+
+      nextMetadataManager.getDeletedDirTable().iterator().forEachRemaining(entry -> count.getAndIncrement());
+      Assertions.assertEquals(15, count.get());
+      count.set(0);
+      nextMetadataManager.getSnapshotRenamedTable().iterator().forEachRemaining(entry -> {
+        try {
+          String expectedValue = renameEntries.getOrDefault(entry.getKey(), entry.getValue());
+          Assertions.assertEquals(expectedValue, entry.getValue());
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+        count.getAndIncrement();
+      });
+      Assertions.assertEquals(15, count.get());
+    }
+
+  }
+}
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestSnapshotDeletingService.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestSnapshotDeletingService.java
index 3948f4fab8..e04891da83 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestSnapshotDeletingService.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestSnapshotDeletingService.java
@@ -20,7 +20,8 @@ package org.apache.hadoop.ozone.om.service;
 
 
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
-import org.apache.hadoop.hdds.scm.protocol.ScmBlockLocationProtocol;
+import org.apache.hadoop.hdds.utils.TransactionInfo;
+import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.ozone.om.KeyManagerImpl;
 import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
 import org.apache.hadoop.ozone.om.OmSnapshotManager;
@@ -56,25 +57,26 @@ public class TestSnapshotDeletingService {
   private SnapshotChainManager chainManager;
   @Mock
   private OmMetadataManagerImpl omMetadataManager;
-  @Mock
-  private ScmBlockLocationProtocol scmClient;
   private final OzoneConfiguration conf = new OzoneConfiguration();;
   private final long sdsRunInterval = Duration.ofMillis(1000).toMillis();
   private final  long sdsServiceTimeout = Duration.ofSeconds(10).toMillis();
 
 
-  private static Stream<Arguments> testCasesForIgnoreSnapshotGc() {
-    SnapshotInfo filteredSnapshot = SnapshotInfo.newBuilder().setSstFiltered(true).setName("snap1").build();
-    SnapshotInfo unFilteredSnapshot = SnapshotInfo.newBuilder().setSstFiltered(false).setName("snap1").build();
+  private static Stream<Arguments> testCasesForIgnoreSnapshotGc() throws IOException {
+    SnapshotInfo flushedSnapshot = SnapshotInfo.newBuilder().setSstFiltered(true)
+        .setLastTransactionInfo(TransactionInfo.valueOf(1, 1).toByteString())
+        .setName("snap1").build();
+    SnapshotInfo unFlushedSnapshot = SnapshotInfo.newBuilder().setSstFiltered(false).setName("snap1")
+        .setLastTransactionInfo(TransactionInfo.valueOf(0, 0).toByteString()).build();
     return Stream.of(
-        Arguments.of(filteredSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_DELETED, false),
-        Arguments.of(filteredSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE, true),
-        Arguments.of(unFilteredSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_DELETED, false),
-        Arguments.of(unFilteredSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE, true),
-        Arguments.of(filteredSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_DELETED, false),
-        Arguments.of(unFilteredSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_DELETED, false),
-        Arguments.of(unFilteredSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE, true),
-        Arguments.of(filteredSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE, true));
+        Arguments.of(flushedSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_DELETED, false),
+        Arguments.of(flushedSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE, true),
+        Arguments.of(unFlushedSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_DELETED, false),
+        Arguments.of(unFlushedSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE, true),
+        Arguments.of(flushedSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_DELETED, false),
+        Arguments.of(unFlushedSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_DELETED, false),
+        Arguments.of(unFlushedSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE, true),
+        Arguments.of(flushedSnapshot, SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE, true));
   }
 
   @ParameterizedTest
@@ -87,9 +89,15 @@ public class TestSnapshotDeletingService {
     Mockito.when(ozoneManager.getOmSnapshotManager()).thenReturn(omSnapshotManager);
     Mockito.when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
     Mockito.when(ozoneManager.getConfiguration()).thenReturn(conf);
+    if (status == SnapshotInfo.SnapshotStatus.SNAPSHOT_DELETED) {
+      Table<String, TransactionInfo> transactionInfoTable = Mockito.mock(Table.class);
+      Mockito.when(omMetadataManager.getTransactionInfoTable()).thenReturn(transactionInfoTable);
+      Mockito.when(transactionInfoTable.getSkipCache(Mockito.anyString()))
+          .thenReturn(TransactionInfo.valueOf(1, 1));
+    }
 
     SnapshotDeletingService snapshotDeletingService =
-        new SnapshotDeletingService(sdsRunInterval, sdsServiceTimeout, ozoneManager, scmClient);
+        new SnapshotDeletingService(sdsRunInterval, sdsServiceTimeout, ozoneManager);
 
     snapshotInfo.setSnapshotStatus(status);
     assertEquals(expectedOutcome, snapshotDeletingService.shouldIgnoreSnapshot(snapshotInfo));
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotChain.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotChain.java
index c5ae809718..f49bfc3397 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotChain.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotChain.java
@@ -18,6 +18,7 @@
 package org.apache.hadoop.ozone.om.snapshot;
 
 import com.google.common.collect.ImmutableMap;
+import org.apache.commons.compress.utils.Lists;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.ozone.om.OMMetadataManager;
@@ -38,6 +39,7 @@ import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -168,6 +170,7 @@ public class TestSnapshotChain {
     }
 
     assertEquals(snapshotID3, chainManager.getLatestGlobalSnapshotId());
+    assertEquals(snapshotID1, chainManager.getOldestGlobalSnapshotId());
     assertEquals(snapshotID3, chainManager.getLatestPathSnapshotId(
         String.join("/", "vol1", "bucket1")));
 
@@ -285,6 +288,7 @@ public class TestSnapshotChain {
     assertFalse(chainManager.isSnapshotChainCorrupted());
     // check if snapshots loaded correctly from snapshotInfoTable
     assertEquals(snapshotID2, chainManager.getLatestGlobalSnapshotId());
+    assertEquals(snapshotID1, chainManager.getOldestGlobalSnapshotId());
     assertEquals(snapshotID2, chainManager.nextGlobalSnapshot(snapshotID1));
     assertEquals(snapshotID1, chainManager.previousPathSnapshot(String
         .join("/", "vol1", "bucket1"), snapshotID2));
@@ -305,6 +309,34 @@ public class TestSnapshotChain {
         () -> chainManager.nextGlobalSnapshot(snapshotID1));
   }
 
+  @ParameterizedTest
+  @ValueSource(ints = {0, 1, 2, 5, 10})
+  public void testSnapshotChainIterator(int numberOfSnapshots) throws IOException {
+    Table<String, SnapshotInfo> snapshotInfo = omMetadataManager.getSnapshotInfoTable();
+    List<SnapshotInfo> snapshotInfoList = new ArrayList<>();
+
+    UUID prevSnapshotID = null;
+    long time = System.currentTimeMillis();
+    for (int i = 0; i < numberOfSnapshots; i++) {
+      UUID snapshotID = UUID.randomUUID();
+      SnapshotInfo snapInfo = createSnapshotInfo(snapshotID, prevSnapshotID,
+          prevSnapshotID, time++);
+      snapshotInfo.put(snapshotID.toString(), snapInfo);
+      prevSnapshotID = snapshotID;
+      snapshotInfoList.add(snapInfo);
+    }
+    chainManager = new SnapshotChainManager(omMetadataManager);
+    assertFalse(chainManager.isSnapshotChainCorrupted());
+    List<UUID> reverseChain = Lists.newArrayList(chainManager.iterator(true));
+    Collections.reverse(reverseChain);
+    List<UUID> forwardChain = Lists.newArrayList(chainManager.iterator(false));
+    List<UUID> expectedChain = snapshotInfoList.stream().map(SnapshotInfo::getSnapshotId).collect(Collectors.toList());
+    assertEquals(expectedChain, reverseChain);
+    assertEquals(expectedChain, forwardChain);
+    assertEquals(forwardChain, reverseChain);
+
+  }
+
   private static Stream<? extends Arguments> invalidSnapshotChain() {
     List<UUID> nodes = IntStream.range(0, 5)
         .mapToObj(i -> UUID.randomUUID())
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotRequestAndResponse.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotRequestAndResponse.java
new file mode 100644
index 0000000000..e60e23de22
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotRequestAndResponse.java
@@ -0,0 +1,255 @@
+/*
+ * 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.om.snapshot;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.hadoop.hdds.client.ReplicationConfig;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.utils.db.BatchOperation;
+import org.apache.hadoop.hdds.utils.db.RDBStore;
+import org.apache.hadoop.ozone.OzoneConfigKeys;
+import org.apache.hadoop.ozone.audit.AuditLogger;
+import org.apache.hadoop.ozone.audit.AuditMessage;
+import org.apache.hadoop.ozone.om.OMConfigKeys;
+import org.apache.hadoop.ozone.om.OMMetrics;
+import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
+import org.apache.hadoop.ozone.om.OmSnapshotManager;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup;
+import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
+import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
+import org.apache.hadoop.ozone.om.request.snapshot.OMSnapshotCreateRequest;
+import org.apache.hadoop.ozone.om.request.snapshot.TestOMSnapshotCreateRequest;
+import org.apache.hadoop.ozone.om.response.snapshot.OMSnapshotCreateResponse;
+import org.apache.hadoop.ozone.om.upgrade.OMLayoutVersionManager;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.apache.hadoop.ozone.om.request.OMRequestTestUtils.createOmKeyInfo;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.framework;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Base class to test snapshot functionalities.
+ */
+public class TestSnapshotRequestAndResponse {
+  @TempDir
+  private File testDir;
+
+  private OzoneManager ozoneManager;
+  private OMMetrics omMetrics;
+  private OmMetadataManagerImpl omMetadataManager;
+  private BatchOperation batchOperation;
+  private OmSnapshotManager omSnapshotManager;
+
+  private String volumeName;
+  private String bucketName;
+  private boolean isAdmin;
+
+  public BatchOperation getBatchOperation() {
+    return batchOperation;
+  }
+
+  public String getBucketName() {
+    return bucketName;
+  }
+
+  public boolean isAdmin() {
+    return isAdmin;
+  }
+
+  public OmMetadataManagerImpl getOmMetadataManager() {
+    return omMetadataManager;
+  }
+
+  public OMMetrics getOmMetrics() {
+    return omMetrics;
+  }
+
+  public OmSnapshotManager getOmSnapshotManager() {
+    return omSnapshotManager;
+  }
+
+  public OzoneManager getOzoneManager() {
+    return ozoneManager;
+  }
+
+  public File getTestDir() {
+    return testDir;
+  }
+
+  public String getVolumeName() {
+    return volumeName;
+  }
+
+  protected TestSnapshotRequestAndResponse() {
+    this.isAdmin = false;
+  }
+
+  protected TestSnapshotRequestAndResponse(boolean isAdmin) {
+    this.isAdmin = isAdmin;
+  }
+
+  @BeforeEach
+  public void baseSetup() throws Exception {
+    ozoneManager = mock(OzoneManager.class);
+    omMetrics = OMMetrics.create();
+    OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
+    ozoneConfiguration.set(OMConfigKeys.OZONE_OM_DB_DIRS,
+        testDir.getAbsolutePath());
+    ozoneConfiguration.set(OzoneConfigKeys.OZONE_METADATA_DIRS,
+        testDir.getAbsolutePath());
+    omMetadataManager = new OmMetadataManagerImpl(ozoneConfiguration,
+        ozoneManager);
+    when(ozoneManager.getConfiguration()).thenReturn(ozoneConfiguration);
+    when(ozoneManager.getMetrics()).thenReturn(omMetrics);
+    when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
+    when(ozoneManager.isRatisEnabled()).thenReturn(true);
+    when(ozoneManager.isFilesystemSnapshotEnabled()).thenReturn(true);
+    when(ozoneManager.isAdmin(any())).thenReturn(isAdmin);
+    when(ozoneManager.isOwner(any(), any())).thenReturn(false);
+    when(ozoneManager.getBucketOwner(any(), any(),
+        any(), any())).thenReturn("dummyBucketOwner");
+    IAccessAuthorizer accessAuthorizer = mock(IAccessAuthorizer.class);
+    when(ozoneManager.getAccessAuthorizer()).thenReturn(accessAuthorizer);
+    when(accessAuthorizer.isNative()).thenReturn(false);
+    OMLayoutVersionManager lvm = mock(OMLayoutVersionManager.class);
+    when(lvm.isAllowed(anyString())).thenReturn(true);
+    when(ozoneManager.getVersionManager()).thenReturn(lvm);
+    AuditLogger auditLogger = mock(AuditLogger.class);
+    when(ozoneManager.getAuditLogger()).thenReturn(auditLogger);
+    doNothing().when(auditLogger).logWrite(any(AuditMessage.class));
+    batchOperation = omMetadataManager.getStore().initBatchOperation();
+
+    volumeName = UUID.randomUUID().toString();
+    bucketName = UUID.randomUUID().toString();
+    OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+        omMetadataManager);
+    omSnapshotManager = new OmSnapshotManager(ozoneManager);
+    when(ozoneManager.getOmSnapshotManager()).thenReturn(omSnapshotManager);
+  }
+
+  @AfterEach
+  public void stop() {
+    omMetrics.unRegister();
+    framework().clearInlineMocks();
+    if (batchOperation != null) {
+      batchOperation.close();
+    }
+  }
+
+  protected Path createSnapshotCheckpoint(String volume, String bucket, String snapshotName) throws Exception {
+    OzoneManagerProtocolProtos.OMRequest omRequest = OMRequestTestUtils
+        .createSnapshotRequest(volume, bucket, snapshotName);
+    // Pre-Execute OMSnapshotCreateRequest.
+    OMSnapshotCreateRequest omSnapshotCreateRequest =
+        TestOMSnapshotCreateRequest.doPreExecute(omRequest, ozoneManager);
+
+    // validateAndUpdateCache OMSnapshotCreateResponse.
+    OMSnapshotCreateResponse omClientResponse = (OMSnapshotCreateResponse)
+        omSnapshotCreateRequest.validateAndUpdateCache(ozoneManager, 1);
+    // Add to batch and commit to DB.
+    try (BatchOperation batchOperation = omMetadataManager.getStore().initBatchOperation()) {
+      omClientResponse.addToDBBatch(omMetadataManager, batchOperation);
+      omMetadataManager.getStore().commitBatchOperation(batchOperation);
+    }
+
+    String key = SnapshotInfo.getTableKey(volume, bucket, snapshotName);
+    SnapshotInfo snapshotInfo =
+        omMetadataManager.getSnapshotInfoTable().get(key);
+    assertNotNull(snapshotInfo);
+
+    RDBStore store = (RDBStore) omMetadataManager.getStore();
+    String checkpointPrefix = store.getDbLocation().getName();
+    Path snapshotDirPath = Paths.get(store.getSnapshotsParentDir(),
+        checkpointPrefix + snapshotInfo.getCheckpointDir());
+    // Check the DB is still there
+    assertTrue(Files.exists(snapshotDirPath));
+    return snapshotDirPath;
+  }
+
+  protected List<Pair<String, List<OmKeyInfo>>> getDeletedKeys(String volume, String bucket,
+                                                               int startRange, int endRange,
+                                                               int numberOfKeys,
+                                                               int minVersion) {
+    return IntStream.range(startRange, endRange).boxed()
+        .map(i -> Pair.of(omMetadataManager.getOzoneDeletePathKey(i,
+                omMetadataManager.getOzoneKey(volume, bucket, "key" + String.format("%010d", i))),
+            IntStream.range(0, numberOfKeys).boxed().map(cnt -> createOmKeyInfo(volume, bucket, "key" + i,
+                    ReplicationConfig.getDefault(ozoneManager.getConfiguration()),
+                    new OmKeyLocationInfoGroup(minVersion + cnt, new ArrayList<>(), false))
+                    .setCreationTime(0).setModificationTime(0).build())
+                .collect(Collectors.toList())))
+        .collect(Collectors.toList());
+  }
+
+  protected List<Pair<String, String>> getRenameKeys(String volume, String bucket,
+                                                     int startRange, int endRange,
+                                                     String renameKeyPrefix) {
+    return IntStream.range(startRange, endRange).boxed()
+        .map(i -> {
+          try {
+            return Pair.of(omMetadataManager.getRenameKey(volume, bucket, i),
+                omMetadataManager.getOzoneKeyFSO(volume, bucket, renameKeyPrefix + i));
+          } catch (IOException e) {
+            throw new RuntimeException(e);
+          }
+        }).collect(Collectors.toList());
+  }
+
+  protected List<Pair<String, List<OmKeyInfo>>> getDeletedDirKeys(String volume, String bucket,
+                                                                  int startRange, int endRange, int numberOfKeys) {
+    return IntStream.range(startRange, endRange).boxed()
+        .map(i -> {
+          try {
+            return Pair.of(omMetadataManager.getOzoneDeletePathKey(i,
+                    omMetadataManager.getOzoneKeyFSO(volume, bucket, "1/key" + i)),
+                IntStream.range(0, numberOfKeys).boxed().map(cnt -> createOmKeyInfo(volume, bucket, "key" + i,
+                        ReplicationConfig.getDefault(ozoneManager.getConfiguration())).build())
+                    .collect(Collectors.toList()));
+          } catch (IOException e) {
+            throw new RuntimeException(e);
+          }
+        })
+        .collect(Collectors.toList());
+  }
+
+}


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