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

peterxcli 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 66ae5d34402 HDDS-13041. Add basic snapshot diff report test for object 
tag, stream key, rewriteKey (#8923)
66ae5d34402 is described below

commit 66ae5d34402761bc34461ba2a01f1f41fe5bba20
Author: Bolin Lin <[email protected]>
AuthorDate: Mon Aug 18 10:13:38 2025 -0400

    HDDS-13041. Add basic snapshot diff report test for object tag, stream key, 
rewriteKey (#8923)
---
 .../hadoop/ozone/om/snapshot/TestOmSnapshot.java   | 1213 ++++++++++++++++++++
 1 file changed, 1213 insertions(+)

diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestOmSnapshot.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestOmSnapshot.java
index 038fc491fe7..aac18d5d36d 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestOmSnapshot.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestOmSnapshot.java
@@ -93,6 +93,7 @@
 import org.apache.hadoop.fs.permission.FsPermission;
 import org.apache.hadoop.hdds.StringUtils;
 import org.apache.hadoop.hdds.client.OzoneQuota;
+import org.apache.hadoop.hdds.client.ReplicationConfig;
 import org.apache.hadoop.hdds.client.StandaloneReplicationConfig;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
@@ -118,9 +119,12 @@
 import org.apache.hadoop.ozone.client.OzoneClient;
 import org.apache.hadoop.ozone.client.OzoneKey;
 import org.apache.hadoop.ozone.client.OzoneKeyDetails;
+import org.apache.hadoop.ozone.client.OzoneMultipartUploadList;
+import org.apache.hadoop.ozone.client.OzoneMultipartUploadPartListParts;
 import org.apache.hadoop.ozone.client.OzoneSnapshot;
 import org.apache.hadoop.ozone.client.OzoneSnapshotDiff;
 import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.apache.hadoop.ozone.client.io.OzoneDataStreamOutput;
 import org.apache.hadoop.ozone.client.io.OzoneInputStream;
 import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
 import org.apache.hadoop.ozone.om.KeyManagerImpl;
@@ -134,6 +138,7 @@
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
 import org.apache.hadoop.ozone.om.helpers.KeyInfoWithVolumeContext;
 import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartInfo;
 import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
 import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
@@ -175,6 +180,7 @@ public abstract class TestOmSnapshot {
       Pattern.compile(SNAPSHOT_KEY_PATTERN_STRING);
   private static final int POLL_INTERVAL_MILLIS = 500;
   private static final int POLL_MAX_WAIT_MILLIS = 120_000;
+  private static final int MIN_PART_SIZE = 5 * 1024 * 1024;
 
   private MiniOzoneCluster cluster;
   private OzoneClient client;
@@ -2676,4 +2682,1211 @@ public void testSnapdiffWithNoOpAPI() throws Exception 
{
       assertEquals(0, diff.getDiffList().size());
     }
   }
+
+  @Test
+  public void testSnapshotdiffWithObjectTagModification() throws Exception {
+    String testVolumeName = "vol" + counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String snap1 = "snap1";
+    String key1 = "k1";
+    key1 = createFileKeyWithPrefix(bucket, key1);
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    Map<String, String> tags = new HashMap<>();
+    tags.put("environment", "test");
+    tags.put("owner", "testuser");
+    bucket.putObjectTagging(key1, tags);
+
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+    SnapshotDiffReport diff = getSnapDiffReport(testVolumeName, testBucketName,
+        snap1, snap2);
+
+    List<DiffReportEntry> diffEntries = Lists.newArrayList(
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.MODIFY,
+            key1));
+    assertEquals(diffEntries, diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapdiffWithMultipleTagOperations() throws Exception {
+    String testVolumeName = "vol" + counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String key1 = "k1";
+    key1 = createFileKeyWithPrefix(bucket, key1);
+
+    Map<String, String> initialTags = new HashMap<>();
+    initialTags.put("version", "1.0");
+    initialTags.put("team", "dev");
+
+    bucket.putObjectTagging(key1, initialTags);
+
+    String snap1 = "snap1";
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    Map<String, String> updatedTags = new HashMap<>();
+    updatedTags.put("version", "2.0");
+    updatedTags.put("team", "dev");
+    updatedTags.put("priority", "high");
+
+    bucket.putObjectTagging(key1, updatedTags);
+
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+    SnapshotDiffReport diff = getSnapDiffReport(testVolumeName, testBucketName,
+        snap1, snap2);
+
+    List<DiffReportEntry> diffEntries = Lists.newArrayList(
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.MODIFY,
+            key1));
+    assertEquals(diffEntries, diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapdiffWithTagRemoval() throws Exception {
+    String testVolumeName = "vol" + counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String key1 = "k1";
+    key1 = createFileKeyWithPrefix(bucket, key1);
+
+    Map<String, String> initialTags = new HashMap<>();
+    initialTags.put("env", "staging");
+    initialTags.put("cost-center", "engineering");
+    initialTags.put("backup", "daily");
+
+    bucket.putObjectTagging(key1, initialTags);
+
+    String snap1 = "snap1";
+    createSnapshot(testVolumeName, testBucketName, snap1);
+    bucket.deleteObjectTagging(key1);
+
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+    SnapshotDiffReport diff = getSnapDiffReport(testVolumeName, testBucketName,
+        snap1, snap2);
+
+    List<DiffReportEntry> diffEntries = Lists.newArrayList(
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.MODIFY,
+            key1));
+    assertEquals(diffEntries, diff.getDiffList());
+  }
+
+  private String createStreamKeyWithPrefix(OzoneBucket bucket, String 
keyPrefix)
+      throws Exception {
+    String key = keyPrefix + counter.incrementAndGet();
+    return createStreamKey(bucket, key);
+  }
+
+  private String createStreamKey(OzoneBucket bucket, String key)
+      throws Exception {
+    byte[] value = RandomStringUtils.secure().nextAscii(10240).getBytes(UTF_8);
+    OzoneDataStreamOutput streamKey = bucket.createStreamKey(key, 
value.length);
+    streamKey.write(value);
+    streamKey.close();
+    GenericTestUtils.waitFor(() -> {
+      try {
+        getOmKeyInfo(bucket.getVolumeName(), bucket.getName(), key);
+      } catch (IOException e) {
+        return false;
+      }
+      return true;
+    }, 1000, 30000);
+    return key;
+  }
+
+  @Test
+  public void testSnapDiffWithStreamKeyModification() throws Exception {
+    String testVolumeName = "vol" + counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String key1 = "stream-key-1";
+    key1 = createStreamKeyWithPrefix(bucket, key1);
+    String snap1 = "snap1";
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String key2 = "stream-key-2";
+    key2 = createStreamKeyWithPrefix(bucket, key2);
+    bucket.deleteKey(key1);
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff =  getSnapDiffReport(testVolumeName, 
testBucketName,
+        snap1, snap2);
+    assertEquals(2, diff.getDiffList().size());
+    assertEquals(Arrays.asList(SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.DELETE, key1),
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.CREATE, key2)), diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapDiffWithStreamKeyRewrite() throws Exception {
+    String testVolumeName = "vol" + counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String key1 = "stream-key-1";
+    key1 = createStreamKeyWithPrefix(bucket, key1);
+    String snap1 = "snap1";
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    createStreamKey(bucket, key1);
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+    assertEquals(1, diff.getDiffList().size());
+    assertEquals(Collections.singletonList(
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.MODIFY, 
key1)),
+        diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapDiffWithStreamKeyRename() throws Exception {
+    String testVolumeName = "vol" + counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String key1 = "stream-key-1";
+    key1 = createStreamKeyWithPrefix(bucket, key1);
+    String snap1 = "snap1";
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String renamedKey = key1 + "_renamed";
+    bucket.renameKey(key1, renamedKey);
+    GenericTestUtils.waitFor(() -> {
+      try {
+        getOmKeyInfo(testVolumeName, testBucketName, renamedKey);
+      } catch (IOException e) {
+        return false;
+      }
+      return true;
+    }, 1000, 10000);
+
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName,
+        testBucketName, snap1, snap2);
+    assertEquals(1, diff.getDiffList().size());
+    assertEquals(Collections.singletonList(
+            SnapshotDiffReportOzone.getDiffReportEntry(
+                SnapshotDiffReport.DiffType.RENAME, key1, renamedKey)),
+        diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapDiffWithStreamKeyRecreation() throws Exception {
+    String testVolumeName = "vol" +  counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String key1 = "stream-key-1";
+    key1 = createStreamKeyWithPrefix(bucket, key1);
+    String snap1 = "snap1";
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    getOmKeyInfo(testVolumeName, testBucketName, key1);
+    bucket.deleteKey(key1);
+    key1 = createStreamKey(bucket, key1);
+    getOmKeyInfo(testVolumeName, testBucketName, key1);
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName,
+        testBucketName, snap1, snap2);
+    assertEquals(2, diff.getDiffList().size());
+    assertEquals(Arrays.asList(
+            SnapshotDiffReportOzone.getDiffReportEntry(
+                SnapshotDiffReport.DiffType.DELETE, key1),
+            SnapshotDiffReportOzone.getDiffReportEntry(
+                SnapshotDiffReport.DiffType.CREATE, key1)),
+        diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapDiffWithMixedStreamAndRegularKeys() throws Exception {
+    String testVolumeName = "vol" +  counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String regularKey1 = "regular-key-1";
+    String streamKey1 = "stream-key-1";
+    regularKey1 = createFileKeyWithPrefix(bucket, regularKey1);
+    streamKey1 = createStreamKeyWithPrefix(bucket, streamKey1);
+    String snap1 = "snap1";
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String regularKey2 = "regular-key-2";
+    String streamKey2 = "stream-key-2";
+    regularKey2 = createFileKeyWithPrefix(bucket, regularKey2);
+    streamKey2 = createStreamKeyWithPrefix(bucket, streamKey2);
+    bucket.deleteKey(regularKey1);
+    bucket.deleteKey(streamKey1);
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName,
+        testBucketName, snap1, snap2);
+    assertEquals(4, diff.getDiffList().size());
+    List<SnapshotDiffReport.DiffReportEntry> expectedDiffs = Arrays.asList(
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.DELETE, regularKey1),
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.DELETE, streamKey1),
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.CREATE, regularKey2),
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.CREATE, streamKey2));
+    assertEquals(expectedDiffs, diff.getDiffList());
+  }
+
+  private String createStreamFileWithPrefix(OzoneBucket bucket, String 
filePrefix)
+      throws Exception {
+    String file = filePrefix + counter.incrementAndGet();
+    return createStreamFile(bucket, file, false);
+  }
+
+  private String createStreamFile(OzoneBucket bucket, String fileName, boolean 
overWrite)
+      throws Exception {
+    byte[] value = RandomStringUtils.secure().nextAscii(10240).getBytes(UTF_8);
+    OzoneDataStreamOutput streamFile = bucket.createStreamFile(
+        fileName, value.length, bucket.getReplicationConfig(), overWrite, 
true);
+    streamFile.write(value);
+    streamFile.close();
+    GenericTestUtils.waitFor(() -> {
+      try {
+        getOmKeyInfo(bucket.getVolumeName(), bucket.getName(), fileName);
+      } catch (IOException e) {
+        return false;
+      }
+      return true;
+    }, 300, 10000);
+    return fileName;
+  }
+
+  @Test
+  public void testSnapDiffWithStreamFileModification() throws Exception {
+    assumeTrue(!bucketLayout.isObjectStore(enabledFileSystemPaths));
+
+    String testVolumeName = "vol" + counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String file1 = "stream-file-1";
+    file1 = createStreamFileWithPrefix(bucket, file1);
+    String snap1 = "snap1";
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String file2 = "stream-file-2";
+    file2 = createStreamFileWithPrefix(bucket, file2);
+    bucket.deleteKey(file1);
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName,
+        testBucketName, snap1, snap2);
+    assertEquals(2, diff.getDiffList().size());
+    assertEquals(Arrays.asList(
+            SnapshotDiffReportOzone.getDiffReportEntry(
+                SnapshotDiffReport.DiffType.DELETE, file1),
+            SnapshotDiffReportOzone.getDiffReportEntry(
+                SnapshotDiffReport.DiffType.CREATE, file2)),
+        diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapDiffWithStreamFileRewrite() throws Exception {
+    assumeTrue(!bucketLayout.isObjectStore(enabledFileSystemPaths));
+
+    String testVolumeName = "vol" + counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String file1 = "stream-file-1";
+    file1 = createStreamFileWithPrefix(bucket, file1);
+    String snap1 = "snap1";
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    createStreamFile(bucket, file1, true);
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName,
+        testBucketName, snap1, snap2);
+    assertEquals(1, diff.getDiffList().size());
+    assertEquals(Collections.singletonList(
+            SnapshotDiffReportOzone.getDiffReportEntry(
+                SnapshotDiffReport.DiffType.MODIFY, file1)),
+        diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapDiffWithStreamFileRename() throws Exception {
+    assumeTrue(!bucketLayout.isObjectStore(enabledFileSystemPaths));
+
+    String testVolumeName = "vol" + counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String file1 = "stream-file-1";
+    file1 = createStreamFileWithPrefix(bucket, file1);
+    String snap1 = "snap1";
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String renamedFile = file1 + "_renamed";
+    bucket.renameKey(file1, renamedFile);
+    GenericTestUtils.waitFor(() -> {
+      try {
+        getOmKeyInfo(testVolumeName, testBucketName, renamedFile);
+      } catch (IOException e) {
+        return false;
+      }
+      return true;
+    }, 1000, 30000);
+
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName,
+        testBucketName, snap1, snap2);
+    assertEquals(1, diff.getDiffList().size());
+    assertEquals(Collections.singletonList(
+            SnapshotDiffReportOzone.getDiffReportEntry(
+                SnapshotDiffReport.DiffType.RENAME, file1, renamedFile)),
+        diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapDiffWithStreamFileRecreation() throws Exception {
+    assumeTrue(!bucketLayout.isObjectStore(enabledFileSystemPaths));
+
+    String testVolumeName = "vol" + counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String file1 = "stream-file-1";
+    file1 = createStreamFileWithPrefix(bucket, file1);
+    String snap1 = "snap1";
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    getOmKeyInfo(testVolumeName, testBucketName, file1);
+    bucket.deleteKey(file1);
+    file1 = createStreamFile(bucket, file1, true);
+    getOmKeyInfo(testVolumeName, testBucketName, file1);
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName,
+        testBucketName, snap1, snap2);
+    assertEquals(2, diff.getDiffList().size());
+    assertEquals(Arrays.asList(
+            SnapshotDiffReportOzone.getDiffReportEntry(
+                SnapshotDiffReport.DiffType.DELETE, file1),
+            SnapshotDiffReportOzone.getDiffReportEntry(
+                SnapshotDiffReport.DiffType.CREATE, file1)),
+        diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapDiffWithStreamFileAndDirectoryOperations() throws 
Exception {
+    assumeTrue(bucketLayout.isFileSystemOptimized());
+    String testVolumeName = "vol" + counter.incrementAndGet();
+    String testBucketName = "bucket1";
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    bucket.createDirectory("dir1/dir2");
+    String file1 = "dir1/dir2/stream-file-1";
+    createStreamFile(bucket, file1, false);
+    String snap1 = "snap1";
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String file2 = "dir1/dir2/stream-file-2";
+    createStreamFile(bucket, file2, false);
+    bucket.renameKey("dir1/dir2", "dir1/dir3");
+    String snap2 = "snap2";
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName,
+        testBucketName, snap1, snap2);
+    assertEquals(3, diff.getDiffList().size());
+
+    List<SnapshotDiffReport.DiffReportEntry> expectedDiffs = Arrays.asList(
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.RENAME, "dir1/dir2", "dir1/dir3"),
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.CREATE, "dir1/dir3/stream-file-2"),
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.MODIFY, "dir1"));
+    assertEquals(expectedDiffs, diff.getDiffList());
+  }
+
+  private ReplicationConfig getDefaultReplication() {
+    return 
StandaloneReplicationConfig.getInstance(HddsProtos.ReplicationFactor.ONE);
+  }
+
+  @Test
+  public void testSnapshotDiffWithInitiateMultipartUpload() throws Exception {
+    String testVolumeName = "vol-initiate" + counter.incrementAndGet();
+    String testBucketName = "bucket-initiate-" + counter.incrementAndGet();
+
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String snap1 = "snap-before-initiate-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String mpuKey1 = "mpu-key-1" + counter.incrementAndGet();
+    String mpuKey2 = "mpu-key-2" + counter.incrementAndGet();
+
+    Map<String, String> metadata = new HashMap<>();
+    metadata.put("test-metadata", "initiate-test");
+
+    Map<String, String> tags = new HashMap<>();
+    tags.put("environment", "test");
+    tags.put("operation", "initiate");
+
+    OmMultipartInfo mpuInfo1 = bucket.initiateMultipartUpload(mpuKey1);
+    OmMultipartInfo mpuInfo2 = bucket.initiateMultipartUpload(mpuKey2, 
getDefaultReplication(), metadata, tags);
+
+    String snap2 = "snap-after-initiate-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+    assertEquals(0, diff.getDiffList().size());
+
+    OzoneMultipartUploadList mpuList = bucket.
+        listMultipartUploads("", null, null, 100);
+    assertEquals(2, mpuList.getUploads().size());
+
+    bucket.abortMultipartUpload(mpuKey1, mpuInfo1.getUploadID());
+    bucket.abortMultipartUpload(mpuKey2, mpuInfo2.getUploadID());
+  }
+
+  @Test
+  public void testSnapshotDiffWithCreateMultipartKeys() throws Exception {
+    String testVolumeName = "vol-create-part-" + counter.incrementAndGet();
+    String testBucketName = "bucket-create-part-" + counter.incrementAndGet();
+
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String regularPartsKey = "regular-parts-key" + counter.incrementAndGet();
+    String streamPartsKey = "stream-parts-key" + counter.incrementAndGet();
+    String mixedPartsKey = "mixed-parts-key" + counter.incrementAndGet();
+
+    OmMultipartInfo regularMpuInfo = 
bucket.initiateMultipartUpload(regularPartsKey, getDefaultReplication());
+    OmMultipartInfo streamMpuInfo = 
bucket.initiateMultipartUpload(streamPartsKey, getDefaultReplication());
+    OmMultipartInfo mixedMpuInfo = 
bucket.initiateMultipartUpload(mixedPartsKey, getDefaultReplication());
+
+    String snap1 = "snap-before-parts-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    byte[] regularPart = "regular multipart key".getBytes(UTF_8);
+    try (OzoneOutputStream stream = bucket.createMultipartKey(
+        regularPartsKey, regularPart.length, 1, regularMpuInfo.getUploadID())) 
{
+      stream.write(regularPart);
+    }
+
+    byte[] streamPart = "stream data".getBytes(UTF_8);
+    try (OzoneDataStreamOutput streamOut = bucket.createMultipartStreamKey(
+        streamPartsKey, streamPart.length, 1, streamMpuInfo.getUploadID())) {
+      streamOut.write(streamPart);
+    }
+
+    byte[] mixedPart = "mixed data".getBytes(UTF_8);
+    try (OzoneOutputStream mixedStream = bucket.createMultipartKey(
+        mixedPartsKey, mixedPart.length, 1, mixedMpuInfo.getUploadID())) {
+      mixedStream.write(mixedPart);
+    }
+
+    assertEquals(1,
+        bucket.listParts(regularPartsKey, regularMpuInfo.getUploadID(), 0, 
100).getPartInfoList().size());
+    assertEquals(1,
+        bucket.listParts(streamPartsKey, streamMpuInfo.getUploadID(), 0, 
100).getPartInfoList().size());
+    assertEquals(1,
+        bucket.listParts(mixedPartsKey, mixedMpuInfo.getUploadID(), 0, 
100).getPartInfoList().size());
+
+    String snap2 = "snap-after-parts-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+    assertEquals(Collections.emptyList(), diff.getDiffList());
+
+    bucket.abortMultipartUpload(regularPartsKey, regularMpuInfo.getUploadID());
+    bucket.abortMultipartUpload(streamPartsKey, streamMpuInfo.getUploadID());
+    bucket.abortMultipartUpload(mixedPartsKey, mixedMpuInfo.getUploadID());
+  }
+
+  @Test
+  public void testSnapshotDiffWithAbortMultipartUpload() throws Exception {
+    String testVolumeName = "vol-abort-" + counter.incrementAndGet();
+    String testBucketName = "bucket-abort-" + counter.incrementAndGet();
+
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String snap1 = "snap-before-abort-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    // Abort immediately after initiate
+    String immediateAbortKey = "immediate-abort-" + counter.incrementAndGet();
+    OmMultipartInfo immediateInfo = 
bucket.initiateMultipartUpload(immediateAbortKey, getDefaultReplication());
+    bucket.abortMultipartUpload(immediateAbortKey, 
immediateInfo.getUploadID());
+
+    // Abort after uploading some parts
+    String partialAbortKey = "partial-abort-" + counter.incrementAndGet();
+    OmMultipartInfo partialInfo = 
bucket.initiateMultipartUpload(partialAbortKey, getDefaultReplication());
+
+    byte[] part1Data = "Part 1 - will be aborted".getBytes(UTF_8);
+    byte[] part2Data = "Part 2 - will be aborted".getBytes(UTF_8);
+    byte[] part3Data = "Part 3 - stream part, will be aborted".getBytes(UTF_8);
+
+    try (OzoneOutputStream part1Stream = bucket.createMultipartKey(
+        partialAbortKey, part1Data.length, 1, partialInfo.getUploadID())) {
+      part1Stream.write(part1Data);
+    }
+    try (OzoneOutputStream part2Stream = bucket.createMultipartKey(
+        partialAbortKey, part2Data.length, 2, partialInfo.getUploadID())) {
+      part2Stream.write(part2Data);
+    }
+    try (OzoneDataStreamOutput part3Stream = bucket.createMultipartStreamKey(
+        partialAbortKey, part3Data.length, 3, partialInfo.getUploadID())) {
+      part3Stream.write(part3Data);
+    }
+
+    OzoneMultipartUploadPartListParts partsList = bucket.listParts(
+        partialAbortKey, partialInfo.getUploadID(), 0, 100);
+    assertEquals(3, partsList.getPartInfoList().size());
+
+    bucket.abortMultipartUpload(partialAbortKey, partialInfo.getUploadID());
+
+    // Multiple aborts in same snapshot window
+    String multiAbortKey1 = "multi-abort-1-" + counter.incrementAndGet();
+    String multiAbortKey2 = "multi-abort-2-" + counter.incrementAndGet();
+
+    OmMultipartInfo multiInfo1 = 
bucket.initiateMultipartUpload(multiAbortKey1, getDefaultReplication());
+    OmMultipartInfo multiInfo2 = 
bucket.initiateMultipartUpload(multiAbortKey2, getDefaultReplication());
+
+    try (OzoneOutputStream stream = bucket.createMultipartKey(
+        multiAbortKey1, part1Data.length, 1, multiInfo1.getUploadID())) {
+      stream.write(part1Data);
+    }
+    try (OzoneDataStreamOutput stream = bucket.createMultipartStreamKey(
+        multiAbortKey2, part2Data.length, 1, multiInfo2.getUploadID())) {
+      stream.write(part2Data);
+    }
+
+    bucket.abortMultipartUpload(multiAbortKey1, multiInfo1.getUploadID());
+    bucket.abortMultipartUpload(multiAbortKey2, multiInfo2.getUploadID());
+
+    String snap2 = "snap-after-aborts-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+    assertEquals(Collections.emptyList(), diff.getDiffList());
+
+    OzoneMultipartUploadList finalMpuList = bucket.listMultipartUploads("", 
null, null, 100);
+    assertEquals(0, finalMpuList.getUploads().size());
+  }
+
+  @Test
+  public void testSnapshotDiffWithCompleteInvisibleMPULifecycle() throws 
Exception {
+    String testVolumeName = "vol-invisible-" + counter.incrementAndGet();
+    String testBucketName = "bucket-invisible-" + counter.incrementAndGet();
+
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String regularKey1 = "regular-before-" + counter.incrementAndGet();
+    String regularKey2 = "regular-after-" + counter.incrementAndGet();
+    createFileKey(bucket, regularKey1);
+
+    String snap1 = "snap-invisible-baseline-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String mpuKey1 = "invisible-mpu-1-" + counter.incrementAndGet();
+    String mpuKey2 = "invisible-mpu-2-" + counter.incrementAndGet();
+    String mpuKey3 = "invisible-mpu-3-" + counter.incrementAndGet();
+
+    OmMultipartInfo mpuInfo1 = bucket.initiateMultipartUpload(mpuKey1, 
getDefaultReplication());
+    OmMultipartInfo mpuInfo2 = bucket.initiateMultipartUpload(mpuKey2, 
getDefaultReplication());
+    OmMultipartInfo mpuInfo3 = bucket.initiateMultipartUpload(mpuKey3, 
getDefaultReplication());
+
+    byte[] regularData1 = "Regular multipart data 1".getBytes(UTF_8);
+    byte[] regularData2 = "Regular multipart data 2".getBytes(UTF_8);
+
+    try (OzoneOutputStream stream = bucket.createMultipartKey(
+        mpuKey1, regularData1.length, 1, mpuInfo1.getUploadID())) {
+      stream.write(regularData1);
+    }
+    try (OzoneOutputStream stream = bucket.createMultipartKey(
+        mpuKey1, regularData2.length, 2, mpuInfo1.getUploadID())) {
+      stream.write(regularData2);
+    }
+
+    byte[] streamData1 = "Stream multipart data 1".getBytes(UTF_8);
+    byte[] streamData2 = "Stream multipart data 2".getBytes(UTF_8);
+
+    try (OzoneDataStreamOutput stream = bucket.createMultipartStreamKey(
+        mpuKey2, streamData1.length, 1, mpuInfo2.getUploadID())) {
+      stream.write(streamData1);
+    }
+    try (OzoneDataStreamOutput stream = bucket.createMultipartStreamKey(
+        mpuKey2, streamData2.length, 2, mpuInfo2.getUploadID())) {
+      stream.write(streamData2);
+    }
+
+
+    byte[] mixedRegular = "Mixed - regular part".getBytes(UTF_8);
+    byte[] mixedStream = "Mixed - stream part".getBytes(UTF_8);
+
+    try (OzoneOutputStream stream = bucket.createMultipartKey(
+        mpuKey3, mixedRegular.length, 1, mpuInfo3.getUploadID())) {
+      stream.write(mixedRegular);
+    }
+    try (OzoneDataStreamOutput stream = bucket.createMultipartStreamKey(
+        mpuKey3, mixedStream.length, 2, mpuInfo3.getUploadID())) {
+      stream.write(mixedStream);
+    }
+
+    assertEquals(2,
+        bucket.listParts(mpuKey1, mpuInfo1.getUploadID(), 0, 
100).getPartInfoList().size());
+    assertEquals(2,
+        bucket.listParts(mpuKey2, mpuInfo2.getUploadID(), 0, 
100).getPartInfoList().size());
+    assertEquals(2,
+        bucket.listParts(mpuKey3, mpuInfo3.getUploadID(), 0, 
100).getPartInfoList().size());
+
+    createFileKey(bucket, regularKey2);
+
+    bucket.abortMultipartUpload(mpuKey1, mpuInfo1.getUploadID());
+    bucket.abortMultipartUpload(mpuKey2, mpuInfo2.getUploadID());
+    bucket.abortMultipartUpload(mpuKey3, mpuInfo3.getUploadID());
+
+    String snap2 = "snap-after-invisible-lifecycle-" + 
counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+
+    List<SnapshotDiffReport.DiffReportEntry> expectedDiffs = Arrays.asList(
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.CREATE, regularKey2));
+    assertEquals(expectedDiffs, diff.getDiffList());
+
+    OzoneMultipartUploadList finalMpuList = bucket.listMultipartUploads("", 
null, null, 100);
+    assertEquals(0, finalMpuList.getUploads().size());
+
+    assertDoesNotThrow(() -> bucket.getKey(regularKey1));
+    assertDoesNotThrow(() -> bucket.getKey(regularKey2));
+
+    assertThrows(OMException.class, () -> bucket.getKey(mpuKey1));
+    assertThrows(OMException.class, () -> bucket.getKey(mpuKey2));
+    assertThrows(OMException.class, () -> bucket.getKey(mpuKey3));
+  }
+
+  private void completeSinglePartMPU(OzoneBucket bucket, String keyName, 
String data) throws IOException {
+    OmMultipartInfo mpuInfo = bucket.initiateMultipartUpload(keyName, 
getDefaultReplication());
+    String uploadId = mpuInfo.getUploadID();
+
+    byte[] partData = createLargePartData(data, MIN_PART_SIZE);
+    OzoneOutputStream partStream = bucket.createMultipartKey(keyName, 
partData.length, 1, uploadId);
+    partStream.write(partData);
+    partStream.close();
+
+    OzoneMultipartUploadPartListParts partsList = bucket.listParts(keyName, 
uploadId, 0, 100);
+    String realETag = partsList.getPartInfoList().get(0).getPartName();
+
+    Map<Integer, String> partsMap = Collections.singletonMap(1, realETag);
+    bucket.completeMultipartUpload(keyName, uploadId, partsMap);
+  }
+
+  private void completeMultiplePartMPU(
+      OzoneBucket bucket, String keyName, List<String> partDataList) throws 
IOException {
+    OmMultipartInfo mpuInfo = bucket.initiateMultipartUpload(keyName, 
getDefaultReplication());
+    String uploadId = mpuInfo.getUploadID();
+
+    for (int i = 0; i < partDataList.size(); i++) {
+      int partNum = i + 1;
+      byte[] partData = createLargePartData(partDataList.get(i), 
MIN_PART_SIZE);
+
+      try (OzoneOutputStream partStream = bucket.createMultipartKey(
+          keyName, partData.length, partNum, uploadId)) {
+        partStream.write(partData);
+      }
+    }
+
+    OzoneMultipartUploadPartListParts partsList = bucket.
+        listParts(keyName, uploadId, 0, partDataList.size());
+    Map<Integer, String> partsMap = new HashMap<>();
+
+    for (OzoneMultipartUploadPartListParts.PartInfo partInfo : 
partsList.getPartInfoList()) {
+      partsMap.put(partInfo.getPartNumber(), partInfo.getPartName());
+    }
+
+    bucket.completeMultipartUpload(keyName, uploadId, partsMap);
+  }
+
+  private void completeMixedPartMPU(
+      OzoneBucket bucket, String keyName, String regularData, String 
streamData) throws IOException {
+    OmMultipartInfo mpuInfo = bucket.initiateMultipartUpload(keyName, 
getDefaultReplication());
+    String uploadId = mpuInfo.getUploadID();
+
+    byte[] part1Data = createLargePartData(regularData, MIN_PART_SIZE);
+    try (OzoneOutputStream partStream = bucket.createMultipartKey(
+        keyName, part1Data.length, 1, uploadId)) {
+      partStream.write(part1Data);
+    }
+
+    byte[] part2Data = createLargePartData(streamData, MIN_PART_SIZE);
+    try (OzoneDataStreamOutput partStream = bucket.createMultipartStreamKey(
+        keyName, part2Data.length, 2, uploadId)) {
+      partStream.write(part2Data);
+    }
+
+    OzoneMultipartUploadPartListParts partsList = bucket.listParts(keyName, 
uploadId, 0, 2);
+    Map<Integer, String> partsMap = new HashMap<>();
+
+    for (OzoneMultipartUploadPartListParts.PartInfo partInfo : 
partsList.getPartInfoList()) {
+      partsMap.put(partInfo.getPartNumber(), partInfo.getPartName());
+    }
+
+    bucket.completeMultipartUpload(keyName, uploadId, partsMap);
+  }
+
+  private void completeMPUWithReplication(
+      OzoneBucket bucket, String keyName, ReplicationConfig replicationConfig) 
throws IOException {
+    OmMultipartInfo mpuInfo;
+    if (replicationConfig != null) {
+      mpuInfo = bucket.initiateMultipartUpload(keyName, replicationConfig);
+    } else {
+      mpuInfo = bucket.initiateMultipartUpload(keyName);
+    }
+
+    String uploadId = mpuInfo.getUploadID();
+
+    byte[] partData = createLargePartData("Replication test data for " + 
keyName, MIN_PART_SIZE);
+    try (OzoneOutputStream partStream = bucket.createMultipartKey(
+        keyName, partData.length, 1, uploadId)) {
+      partStream.write(partData);
+    }
+
+    OzoneMultipartUploadPartListParts partsList = bucket.listParts(keyName, 
uploadId, 0, 1);
+    String realETag = partsList.getPartInfoList().get(0).getPartName();
+
+    Map<Integer, String> partsMap = Collections.singletonMap(1, realETag);
+    bucket.completeMultipartUpload(keyName, uploadId, partsMap);
+  }
+
+  private void completeMPUWithMetadata(OzoneBucket bucket, String keyName,
+                                       Map<String, String> metadata, 
Map<String, String> tags) throws IOException {
+
+    OmMultipartInfo mpuInfo = bucket.initiateMultipartUpload(
+        keyName, getDefaultReplication(), metadata, tags);
+    String uploadId = mpuInfo.getUploadID();
+
+    byte[] partData = createLargePartData("MPU with metadata and tags", 
MIN_PART_SIZE);
+    OzoneOutputStream partStream = bucket.createMultipartKey(keyName, 
partData.length, 1, uploadId);
+    partStream.write(partData);
+    partStream.close();
+
+    OzoneMultipartUploadPartListParts partsList = bucket.listParts(keyName, 
uploadId, 0, 100);
+    String realETag = partsList.getPartInfoList().get(0).getPartName();
+
+    Map<Integer, String> partsMap = Collections.singletonMap(1, realETag);
+    bucket.completeMultipartUpload(keyName, uploadId, partsMap);
+  }
+
+  private byte[] createLargePartData(String baseContent, int targetSize) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(baseContent);
+
+    String padding = " - padding data";
+    while (sb.length() < targetSize) {
+      sb.append(padding);
+      if (sb.length() + padding.length() > targetSize) {
+        int remaining = targetSize - sb.length();
+        sb.append(padding.substring(0, Math.min(remaining, padding.length())));
+      }
+    }
+
+    String result = sb.toString();
+    if (result.length() > targetSize) {
+      result = result.substring(0, targetSize);
+    }
+
+    return result.getBytes(UTF_8);
+  }
+
+  @Test
+  public void testSnapshotDiffMPUCreateNewKey() throws Exception {
+    String testVolumeName = "vol-create-new-" + counter.incrementAndGet();
+    String testBucketName = "bucket-create-new-" + counter.incrementAndGet();
+
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String existingKey = "existing-baseline-" + counter.incrementAndGet();
+    createFileKey(bucket, existingKey);
+
+    String snap1 = "snap-before-create-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String mpuKey1 = "multi-mpu-1-" + counter.incrementAndGet();
+    String mpuKey2 = "multi-mpu-2-" + counter.incrementAndGet();
+    String mpuKey3 = "multi-mpu-3-" + counter.incrementAndGet();
+
+    completeSinglePartMPU(bucket, mpuKey1, "Single part MPU data");
+
+    completeMultiplePartMPU(bucket, mpuKey2,
+        Arrays.asList("Multi part 1", "Multi part 2", "Multi part 3"));
+
+    completeMixedPartMPU(bucket, mpuKey3,
+        "Mixed regular part", "Mixed stream part");
+
+    String regularKey = "regular-compare-" + counter.incrementAndGet();
+    createFileKey(bucket, regularKey);
+
+    String snap2 = "snap-multiple-final-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+
+    List<SnapshotDiffReport.DiffReportEntry> expectedDiffs = Arrays.asList(
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
mpuKey1),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
mpuKey2),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
mpuKey3),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
regularKey));
+    assertEquals(expectedDiffs, diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapshotDiffMPUCreateMultipleKeys() throws Exception {
+    String testVolumeName = "vol-create-multiple-" + counter.incrementAndGet();
+    String testBucketName = "bucket-create-multiple-" + 
counter.incrementAndGet();
+
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String snap1 = "snap-multiple-baseline-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String mpuKey1 = "multi-mpu-1-" + counter.incrementAndGet();
+    String mpuKey2 = "multi-mpu-2-" + counter.incrementAndGet();
+    String mpuKey3 = "multi-mpu-3-" + counter.incrementAndGet();
+
+    completeSinglePartMPU(bucket, mpuKey1, "Single part MPU data");
+    completeMultiplePartMPU(bucket, mpuKey2,
+        Arrays.asList("Multi part 1", "Multi part 2", "Multi part 3"));
+    completeMixedPartMPU(bucket, mpuKey3,
+        "Mixed regular part", "Mixed stream part");
+
+    String regularKey = "regular-compare-" + counter.incrementAndGet();
+    createFileKey(bucket, regularKey);
+
+    String snap2 = "snap-multiple-final-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+
+    List<SnapshotDiffReport.DiffReportEntry> expectedDiffs = Arrays.asList(
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
mpuKey1),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
mpuKey2),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
mpuKey3),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
regularKey));
+    assertEquals(expectedDiffs, diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapshotDiffMPUCreateWithMetadataAndTags() throws Exception {
+    String testVolumeName = "vol-create-meta-" + counter.incrementAndGet();
+    String testBucketName = "bucket-create-meta-" + counter.incrementAndGet();
+
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String snap1 = "snap-meta-baseline-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String mpuKeyWithMeta = "mpu-with-metadata-" + counter.incrementAndGet();
+
+    Map<String, String> richMetadata = new HashMap<>();
+    richMetadata.put("content-type", "application/octet-stream");
+    richMetadata.put("content-encoding", "gzip");
+    richMetadata.put("cache-control", "no-cache");
+    richMetadata.put("custom-header", "test-value");
+
+    Map<String, String> richTags = new HashMap<>();
+    richTags.put("project", "ozone-testing");
+    richTags.put("environment", "integration");
+    richTags.put("cost-center", "engineering");
+    richTags.put("backup-policy", "daily");
+
+    completeMPUWithMetadata(bucket, mpuKeyWithMeta, richMetadata, richTags);
+
+    String snap2 = "snap-meta-final-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+
+    List<SnapshotDiffReport.DiffReportEntry> expectedDiffs = Arrays.asList(
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.CREATE, mpuKeyWithMeta));
+    assertEquals(expectedDiffs, diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapshotDiffMPUCreateWithDifferentReplication() throws 
Exception {
+    String testVolumeName = "vol-create-repl-" + counter.incrementAndGet();
+    String testBucketName = "bucket-create-repl-" + counter.incrementAndGet();
+
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String snap1 = "snap-repl-baseline-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String standaloneKey = "standalone-mpu-" + counter.incrementAndGet();
+    String defaultKey = "default-mpu-" + counter.incrementAndGet();
+
+    ReplicationConfig standaloneConfig = 
StandaloneReplicationConfig.getInstance(
+        HddsProtos.ReplicationFactor.ONE);
+    completeMPUWithReplication(bucket, standaloneKey, standaloneConfig);
+    completeMPUWithReplication(bucket, defaultKey, null);
+
+    String snap2 = "snap-repl-final-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+
+    List<SnapshotDiffReport.DiffReportEntry> expectedDiffs = Arrays.asList(
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
standaloneKey),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
defaultKey));
+    assertEquals(expectedDiffs, diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapshotDiffMPUModifyExistingKey() throws Exception {
+    String testVolumeName = "vol-modify-existing-" + counter.incrementAndGet();
+    String testBucketName = "bucket-modify-existing-" + 
counter.incrementAndGet();
+
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String existingKey = "existing-key-" + counter.incrementAndGet();
+    createFileKey(bucket, existingKey);
+
+    String snap1 = "snap-before-modify-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    completeSinglePartMPU(bucket, existingKey, "MPU overwritten content");
+
+    String snap2 = "snap-after-modify-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+
+    List<SnapshotDiffReport.DiffReportEntry> expectedDiffs = 
Collections.singletonList(
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.MODIFY, existingKey));
+    assertEquals(expectedDiffs, diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapshotDiffMPUModifyMultipleKeys() throws Exception {
+    String testVolumeName = "vol-modify-multiple-" + counter.incrementAndGet();
+    String testBucketName = "bucket-modify-multiple-" + 
counter.incrementAndGet();
+
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String existingKey1 = "existing-1-" + counter.incrementAndGet();
+    String existingKey2 = "existing-2-" + counter.incrementAndGet();
+    String existingKey3 = "existing-3-" + counter.incrementAndGet();
+    String unchangedKey = "unchanged-" + counter.incrementAndGet();
+
+    createFileKey(bucket, existingKey1);
+    createFileKey(bucket, existingKey2);
+    createFileKey(bucket, existingKey3);
+    createFileKey(bucket, unchangedKey);
+
+    String snap1 = "snap-modify-multiple-baseline-" + 
counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    completeSinglePartMPU(bucket, existingKey1, "Single part overwrite");
+    completeMultiplePartMPU(bucket, existingKey2,
+        Arrays.asList("Multi part overwrite 1", "Multi part overwrite 2"));
+    completeMixedPartMPU(bucket, existingKey3,
+        "Mixed regular overwrite", "Mixed stream overwrite");
+
+    String snap2 = "snap-modify-multiple-final-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+
+    List<SnapshotDiffReport.DiffReportEntry> expectedDiffs = Arrays.asList(
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.MODIFY, 
existingKey1),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.MODIFY, 
existingKey2),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.MODIFY, 
existingKey3));
+    assertEquals(expectedDiffs, diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapshotDiffMPUModifyWithMetadataChange() throws Exception {
+    String testVolumeName = "vol-modify-meta-" + counter.incrementAndGet();
+    String testBucketName = "bucket-modify-meta-" + counter.incrementAndGet();
+
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String existingKey = "existing-meta-key-" + counter.incrementAndGet();
+    createFileKey(bucket, existingKey);
+
+    Map<String, String> originalTags = new HashMap<>();
+    originalTags.put("version", "1.0");
+    originalTags.put("environment", "test");
+    bucket.putObjectTagging(existingKey, originalTags);
+
+    String snap1 = "snap-modify-meta-baseline-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    Map<String, String> newMetadata = new HashMap<>();
+    newMetadata.put("content-type", "application/json");
+    newMetadata.put("updated-by", "mpu-test");
+
+    Map<String, String> newTags = new HashMap<>();
+    newTags.put("version", "2.0");
+    newTags.put("environment", "updated");
+    newTags.put("method", "mpu-overwrite");
+
+    completeMPUWithMetadata(bucket, existingKey, newMetadata, newTags);
+
+    String snap2 = "snap-modify-meta-final-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+
+    List<SnapshotDiffReport.DiffReportEntry> expectedDiffs = Arrays.asList(
+        SnapshotDiffReportOzone.getDiffReportEntry(
+            SnapshotDiffReport.DiffType.MODIFY, existingKey));
+    assertEquals(expectedDiffs, diff.getDiffList());
+  }
+
+  @Test
+  public void testSnapshotDiffMPUMixedCreateAndModify() throws Exception {
+    String testVolumeName = "vol-mixed-ops-" + counter.incrementAndGet();
+    String testBucketName = "bucket-mixed-ops-" + counter.incrementAndGet();
+
+    store.createVolume(testVolumeName);
+    OzoneVolume volume = store.getVolume(testVolumeName);
+    createBucket(volume, testBucketName);
+    OzoneBucket bucket = volume.getBucket(testBucketName);
+
+    String existingKey1 = "existing-1-" + counter.incrementAndGet();
+    String existingKey2 = "existing-2-" + counter.incrementAndGet();
+    String unchangedKey = "unchanged-" + counter.incrementAndGet();
+
+    createFileKey(bucket, existingKey1);
+    createFileKey(bucket, existingKey2);
+    createFileKey(bucket, unchangedKey);
+
+    String snap1 = "snap-mixed-baseline-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap1);
+
+    String newKey1 = "new-mpu-1-" + counter.incrementAndGet();
+    String newKey2 = "new-mpu-2-" + counter.incrementAndGet();
+
+    completeSinglePartMPU(bucket, newKey1, "New key via single part MPU");
+    completeMixedPartMPU(bucket, newKey2, "New key regular part", "New key 
stream part");
+
+    completeSinglePartMPU(bucket, existingKey1, "Modified via single part 
MPU");
+    completeMultiplePartMPU(bucket, existingKey2,
+        Arrays.asList("Modified part 1", "Modified part 2"));
+
+    String regularNewKey = "regular-new-" + counter.incrementAndGet();
+    createFileKey(bucket, regularNewKey);
+
+    String snap2 = "snap-mixed-final-" + counter.incrementAndGet();
+    createSnapshot(testVolumeName, testBucketName, snap2);
+
+    SnapshotDiffReportOzone diff = getSnapDiffReport(testVolumeName, 
testBucketName, snap1, snap2);
+
+    List<SnapshotDiffReport.DiffReportEntry> expectedDiffs = Arrays.asList(
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
newKey1),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
newKey2),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.CREATE, 
regularNewKey),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.MODIFY, 
existingKey1),
+        
SnapshotDiffReportOzone.getDiffReportEntry(SnapshotDiffReport.DiffType.MODIFY, 
existingKey2));
+    assertEquals(expectedDiffs, diff.getDiffList());
+  }
 }


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

Reply via email to