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 74d4bd220f7 HDDS-13627. In-memory Manager for Snapshot Local Data 
(#9141)
74d4bd220f7 is described below

commit 74d4bd220f757f191089527de2e2b5afeb471b81
Author: Swaminathan Balachandran <[email protected]>
AuthorDate: Tue Oct 14 22:59:28 2025 -0400

    HDDS-13627. In-memory Manager for Snapshot Local Data (#9141)
---
 .../apache/hadoop/ozone/om/OmSnapshotManager.java  |   9 +-
 .../response/snapshot/OMSnapshotPurgeResponse.java |   9 +-
 .../om/snapshot/OmSnapshotLocalDataManager.java    | 190 ++++++++++-
 .../TestOMSnapshotPurgeRequestAndResponse.java     |   2 -
 .../snapshot/TestOmSnapshotLocalDataManager.java   | 374 +++++++++++++++++++++
 .../filter/AbstractReclaimableFilterTest.java      |  11 +-
 6 files changed, 578 insertions(+), 17 deletions(-)

diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshotManager.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshotManager.java
index 19fe367bb92..7b9beb80cf6 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshotManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshotManager.java
@@ -195,7 +195,7 @@ public final class OmSnapshotManager implements 
AutoCloseable {
   private int fsSnapshotMaxLimit;
   private final AtomicInteger inFlightSnapshotCount = new AtomicInteger(0);
 
-  public OmSnapshotManager(OzoneManager ozoneManager) {
+  public OmSnapshotManager(OzoneManager ozoneManager) throws IOException {
     this.snapshotLocalDataManager = new 
OmSnapshotLocalDataManager(ozoneManager.getMetadataManager());
     boolean isFilesystemSnapshotEnabled =
         ozoneManager.isFilesystemSnapshotEnabled();
@@ -803,6 +803,13 @@ public static Path getSnapshotPath(OMMetadataManager 
omMetadataManager, Snapshot
         checkpointPrefix + snapshotInfo.getCheckpointDir());
   }
 
+  public static Path getSnapshotPath(OMMetadataManager omMetadataManager, UUID 
snapshotId) {
+    RDBStore store = (RDBStore) omMetadataManager.getStore();
+    String checkpointPrefix = store.getDbLocation().getName();
+    return Paths.get(store.getSnapshotsParentDir(),
+        checkpointPrefix + SnapshotInfo.getCheckpointDirName(snapshotId));
+  }
+
   public static String getSnapshotPath(OzoneConfiguration conf,
       SnapshotInfo snapshotInfo) {
     return getSnapshotPath(conf, snapshotInfo.getCheckpointDirName());
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotPurgeResponse.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotPurgeResponse.java
index 75ba2a8f950..267547bc1e5 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotPurgeResponse.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/snapshot/OMSnapshotPurgeResponse.java
@@ -23,9 +23,7 @@
 import com.google.common.annotations.VisibleForTesting;
 import jakarta.annotation.Nonnull;
 import java.io.IOException;
-import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.List;
 import java.util.Map;
 import org.apache.commons.io.FileUtils;
@@ -130,14 +128,11 @@ private void 
deleteCheckpointDirectory(OmSnapshotLocalDataManager snapshotLocalD
     boolean acquiredSnapshotLock = omLockDetails.isLockAcquired();
     if (acquiredSnapshotLock) {
       Path snapshotDirPath = 
OmSnapshotManager.getSnapshotPath(omMetadataManager, snapshotInfo);
-      // TODO: Do not delete on snapshot purge. OmSnapshotLocalDataManager 
should delete orphan local data files.
-      Path snapshotLocalDataPath = 
Paths.get(snapshotLocalDataManager.getSnapshotLocalPropertyYamlPath(snapshotInfo));
       try {
         FileUtils.deleteDirectory(snapshotDirPath.toFile());
-        Files.deleteIfExists(snapshotLocalDataPath);
       } catch (IOException ex) {
-        LOG.error("Failed to delete snapshot directory {} and/or local data 
file {} for snapshot {}",
-            snapshotDirPath, snapshotLocalDataPath, 
snapshotInfo.getTableKey(), ex);
+        LOG.error("Failed to delete snapshot directory {} for snapshot {}",
+            snapshotDirPath, snapshotInfo.getTableKey(), ex);
       } finally {
         omMetadataManager.getLock().releaseWriteLock(SNAPSHOT_DB_LOCK, 
snapshotInfo.getSnapshotId().toString());
       }
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/OmSnapshotLocalDataManager.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/OmSnapshotLocalDataManager.java
index 98536444a61..3c529abaf3c 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/OmSnapshotLocalDataManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/OmSnapshotLocalDataManager.java
@@ -19,14 +19,28 @@
 
 import static 
org.apache.hadoop.ozone.om.OmSnapshotLocalDataYaml.YAML_FILE_EXTENSION;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.graph.GraphBuilder;
+import com.google.common.graph.MutableGraph;
 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.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.Stack;
+import java.util.UUID;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.hadoop.hdds.utils.db.RDBStore;
 import org.apache.hadoop.ozone.om.OMMetadataManager;
 import org.apache.hadoop.ozone.om.OmSnapshotLocalData;
+import org.apache.hadoop.ozone.om.OmSnapshotLocalData.VersionMeta;
 import org.apache.hadoop.ozone.om.OmSnapshotLocalDataYaml;
 import org.apache.hadoop.ozone.om.OmSnapshotManager;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
@@ -45,9 +59,12 @@ public class OmSnapshotLocalDataManager implements 
AutoCloseable {
   private static final Logger LOG = 
LoggerFactory.getLogger(OmSnapshotLocalDataManager.class);
 
   private final ObjectSerializer<OmSnapshotLocalData> 
snapshotLocalDataSerializer;
+  private final MutableGraph<LocalDataVersionNode> localDataGraph;
+  private final Map<UUID, SnapshotVersionsMeta> versionNodeMap;
   private final OMMetadataManager omMetadataManager;
 
-  public OmSnapshotLocalDataManager(OMMetadataManager omMetadataManager) {
+  public OmSnapshotLocalDataManager(OMMetadataManager omMetadataManager) 
throws IOException {
+    this.localDataGraph = GraphBuilder.directed().build();
     this.omMetadataManager = omMetadataManager;
     this.snapshotLocalDataSerializer = new YamlSerializer<OmSnapshotLocalData>(
         new OmSnapshotLocalDataYaml.YamlFactory()) {
@@ -57,6 +74,13 @@ public void computeAndSetChecksum(Yaml yaml, 
OmSnapshotLocalData data) throws IO
         data.computeAndSetChecksum(yaml);
       }
     };
+    this.versionNodeMap = new HashMap<>();
+    init();
+  }
+
+  @VisibleForTesting
+  Map<UUID, SnapshotVersionsMeta> getVersionNodeMap() {
+    return versionNodeMap;
   }
 
   /**
@@ -76,7 +100,11 @@ public static String getSnapshotLocalPropertyYamlPath(Path 
snapshotPath) {
    * @return the path to the snapshot's local property YAML file
    */
   public String getSnapshotLocalPropertyYamlPath(SnapshotInfo snapshotInfo) {
-    Path snapshotPath = OmSnapshotManager.getSnapshotPath(omMetadataManager, 
snapshotInfo);
+    return getSnapshotLocalPropertyYamlPath(snapshotInfo.getSnapshotId());
+  }
+
+  public String getSnapshotLocalPropertyYamlPath(UUID snapshotId) {
+    Path snapshotPath = OmSnapshotManager.getSnapshotPath(omMetadataManager, 
snapshotId);
     return getSnapshotLocalPropertyYamlPath(snapshotPath);
   }
 
@@ -95,14 +123,100 @@ public void createNewOmSnapshotLocalDataFile(RDBStore 
snapshotStore, SnapshotInf
   }
 
   public OmSnapshotLocalData getOmSnapshotLocalData(SnapshotInfo snapshotInfo) 
throws IOException {
-    Path snapshotLocalDataPath = 
Paths.get(getSnapshotLocalPropertyYamlPath(snapshotInfo));
-    return snapshotLocalDataSerializer.load(snapshotLocalDataPath.toFile());
+    return getOmSnapshotLocalData(snapshotInfo.getSnapshotId());
+  }
+
+  public OmSnapshotLocalData getOmSnapshotLocalData(UUID snapshotId) throws 
IOException {
+    Path snapshotLocalDataPath = 
Paths.get(getSnapshotLocalPropertyYamlPath(snapshotId));
+    OmSnapshotLocalData snapshotLocalData = 
snapshotLocalDataSerializer.load(snapshotLocalDataPath.toFile());
+    if (!Objects.equals(snapshotLocalData.getSnapshotId(), snapshotId)) {
+      throw new IOException("SnapshotId in path : " + snapshotLocalDataPath + 
" contains snapshotLocalData " +
+          "corresponding to snapshotId " + snapshotLocalData.getSnapshotId() + 
". Expected snapshotId " + snapshotId);
+    }
+    return snapshotLocalData;
   }
 
   public OmSnapshotLocalData getOmSnapshotLocalData(File snapshotDataPath) 
throws IOException {
     return snapshotLocalDataSerializer.load(snapshotDataPath);
   }
 
+  private LocalDataVersionNode getVersionNode(UUID snapshotId, int version) {
+    if (!versionNodeMap.containsKey(snapshotId)) {
+      return null;
+    }
+    return versionNodeMap.get(snapshotId).getVersionNode(version);
+  }
+
+  private void addSnapshotVersionMeta(UUID snapshotId, SnapshotVersionsMeta 
snapshotVersionsMeta)
+      throws IOException {
+    if (!versionNodeMap.containsKey(snapshotId)) {
+      for (LocalDataVersionNode versionNode : 
snapshotVersionsMeta.getSnapshotVersions().values()) {
+        if (getVersionNode(versionNode.snapshotId, versionNode.version) != 
null) {
+          throw new IOException("Unable to add " + versionNode + " since it 
already exists");
+        }
+        LocalDataVersionNode previousVersionNode = 
versionNode.previousSnapshotId == null ? null :
+            getVersionNode(versionNode.previousSnapshotId, 
versionNode.previousSnapshotVersion);
+        if (versionNode.previousSnapshotId != null && previousVersionNode == 
null) {
+          throw new IOException("Unable to add " + versionNode + " since 
previous snapshot with version hasn't been " +
+              "loaded");
+        }
+        localDataGraph.addNode(versionNode);
+        if (previousVersionNode != null) {
+          localDataGraph.putEdge(versionNode, previousVersionNode);
+        }
+      }
+      versionNodeMap.put(snapshotId, snapshotVersionsMeta);
+    }
+  }
+
+  void addVersionNodeWithDependents(OmSnapshotLocalData snapshotLocalData) 
throws IOException {
+    if (versionNodeMap.containsKey(snapshotLocalData.getSnapshotId())) {
+      return;
+    }
+    Set<UUID> visitedSnapshotIds = new HashSet<>();
+    Stack<Pair<UUID, SnapshotVersionsMeta>> stack = new Stack<>();
+    stack.push(Pair.of(snapshotLocalData.getSnapshotId(), new 
SnapshotVersionsMeta(snapshotLocalData)));
+    while (!stack.isEmpty()) {
+      Pair<UUID, SnapshotVersionsMeta> versionNodeToProcess = stack.peek();
+      UUID snapId = versionNodeToProcess.getLeft();
+      SnapshotVersionsMeta snapshotVersionsMeta = 
versionNodeToProcess.getRight();
+      if (visitedSnapshotIds.contains(snapId)) {
+        addSnapshotVersionMeta(snapId, snapshotVersionsMeta);
+        stack.pop();
+      } else {
+        UUID prevSnapId = snapshotVersionsMeta.getPreviousSnapshotId();
+        if (prevSnapId != null && !versionNodeMap.containsKey(prevSnapId)) {
+          OmSnapshotLocalData prevSnapshotLocalData = 
getOmSnapshotLocalData(prevSnapId);
+          stack.push(Pair.of(prevSnapshotLocalData.getSnapshotId(), new 
SnapshotVersionsMeta(prevSnapshotLocalData)));
+        }
+        visitedSnapshotIds.add(snapId);
+      }
+    }
+  }
+
+  private void init() throws IOException {
+    RDBStore store = (RDBStore) omMetadataManager.getStore();
+    String checkpointPrefix = store.getDbLocation().getName();
+    File snapshotDir = new File(store.getSnapshotsParentDir());
+    File[] localDataFiles = snapshotDir.listFiles(
+        (dir, name) -> name.startsWith(checkpointPrefix) && 
name.endsWith(YAML_FILE_EXTENSION));
+    if (localDataFiles == null) {
+      throw new IOException("Error while listing yaml files inside directory: 
" + snapshotDir.getAbsolutePath());
+    }
+    Arrays.sort(localDataFiles, Comparator.comparing(File::getName));
+    for (File localDataFile : localDataFiles) {
+      OmSnapshotLocalData snapshotLocalData = 
snapshotLocalDataSerializer.load(localDataFile);
+      File file = new 
File(getSnapshotLocalPropertyYamlPath(snapshotLocalData.getSnapshotId()));
+      String expectedPath = file.getAbsolutePath();
+      String actualPath = localDataFile.getAbsolutePath();
+      if (!expectedPath.equals(actualPath)) {
+        throw new IOException("Unexpected path for local data file with 
snapshotId:" + snapshotLocalData.getSnapshotId()
+            + " : " + actualPath + ". " + "Expected: " + expectedPath);
+      }
+      addVersionNodeWithDependents(snapshotLocalData);
+    }
+  }
+
   @Override
   public void close() {
     if (snapshotLocalDataSerializer != null) {
@@ -113,4 +227,72 @@ public void close() {
       }
     }
   }
+
+  static final class LocalDataVersionNode {
+    private final UUID snapshotId;
+    private final int version;
+    private final UUID previousSnapshotId;
+    private final int previousSnapshotVersion;
+
+    private LocalDataVersionNode(UUID snapshotId, int version, UUID 
previousSnapshotId, int previousSnapshotVersion) {
+      this.previousSnapshotId = previousSnapshotId;
+      this.previousSnapshotVersion = previousSnapshotVersion;
+      this.snapshotId = snapshotId;
+      this.version = version;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof LocalDataVersionNode)) {
+        return false;
+      }
+      LocalDataVersionNode that = (LocalDataVersionNode) o;
+      return version == that.version && previousSnapshotVersion == 
that.previousSnapshotVersion &&
+          snapshotId.equals(that.snapshotId) && 
Objects.equals(previousSnapshotId, that.previousSnapshotId);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(snapshotId, version, previousSnapshotId, 
previousSnapshotVersion);
+    }
+  }
+
+  static final class SnapshotVersionsMeta {
+    private final UUID previousSnapshotId;
+    private final Map<Integer, LocalDataVersionNode> snapshotVersions;
+    private int version;
+
+    private SnapshotVersionsMeta(OmSnapshotLocalData snapshotLocalData) {
+      this.previousSnapshotId = snapshotLocalData.getPreviousSnapshotId();
+      this.snapshotVersions = getVersionNodes(snapshotLocalData);
+      this.version = snapshotLocalData.getVersion();
+    }
+
+    private Map<Integer, LocalDataVersionNode> 
getVersionNodes(OmSnapshotLocalData snapshotLocalData) {
+      UUID snapshotId = snapshotLocalData.getSnapshotId();
+      UUID prevSnapshotId = snapshotLocalData.getPreviousSnapshotId();
+      Map<Integer, LocalDataVersionNode> versionNodes = new HashMap<>();
+      for (Map.Entry<Integer, VersionMeta> entry : 
snapshotLocalData.getVersionSstFileInfos().entrySet()) {
+        versionNodes.put(entry.getKey(), new LocalDataVersionNode(snapshotId, 
entry.getKey(),
+            prevSnapshotId, entry.getValue().getPreviousSnapshotVersion()));
+      }
+      return versionNodes;
+    }
+
+    UUID getPreviousSnapshotId() {
+      return previousSnapshotId;
+    }
+
+    int getVersion() {
+      return version;
+    }
+
+    Map<Integer, LocalDataVersionNode> getSnapshotVersions() {
+      return snapshotVersions;
+    }
+
+    LocalDataVersionNode getVersionNode(int snapshotVersion) {
+      return snapshotVersions.get(snapshotVersion);
+    }
+  }
 }
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 0fb26a4cd99..35053882eed 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
@@ -190,8 +190,6 @@ public void testValidateAndUpdateCache() throws Exception {
     // Check if all the checkpoints are cleared.
     for (Path checkpoint : checkpointPaths) {
       assertFalse(Files.exists(checkpoint));
-      assertFalse(Files.exists(Paths.get(
-          
OmSnapshotLocalDataManager.getSnapshotLocalPropertyYamlPath(checkpoint))));
     }
     assertEquals(initialSnapshotPurgeCount + 1, 
getOmSnapshotIntMetrics().getNumSnapshotPurges());
     assertEquals(initialSnapshotPurgeFailCount, 
getOmSnapshotIntMetrics().getNumSnapshotPurgeFails());
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestOmSnapshotLocalDataManager.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestOmSnapshotLocalDataManager.java
new file mode 100644
index 00000000000..34bde4814a6
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestOmSnapshotLocalDataManager.java
@@ -0,0 +1,374 @@
+/*
+ * 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 static org.apache.hadoop.ozone.OzoneConsts.OM_SNAPSHOT_SEPARATOR;
+import static 
org.apache.hadoop.ozone.om.OmSnapshotLocalDataYaml.YAML_FILE_EXTENSION;
+import static org.apache.hadoop.ozone.om.codec.OMDBDefinition.DIRECTORY_TABLE;
+import static org.apache.hadoop.ozone.om.codec.OMDBDefinition.FILE_TABLE;
+import static org.apache.hadoop.ozone.om.codec.OMDBDefinition.KEY_TABLE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.hdds.StringUtils;
+import org.apache.hadoop.hdds.utils.db.RDBStore;
+import org.apache.hadoop.hdds.utils.db.RocksDatabase;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OmSnapshotLocalData;
+import org.apache.hadoop.ozone.om.OmSnapshotLocalDataYaml;
+import org.apache.hadoop.ozone.om.OmSnapshotManager;
+import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
+import org.apache.hadoop.ozone.util.YamlSerializer;
+import org.apache.ozone.compaction.log.SstFileInfo;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.rocksdb.LiveFileMetaData;
+import org.yaml.snakeyaml.Yaml;
+
+/**
+ * Test class for OmSnapshotLocalDataManager.
+ */
+public class TestOmSnapshotLocalDataManager {
+
+  private static YamlSerializer<OmSnapshotLocalData> 
snapshotLocalDataYamlSerializer;
+
+  @Mock
+  private OMMetadataManager omMetadataManager;
+
+  @Mock
+  private RDBStore rdbStore;
+
+  @Mock
+  private RDBStore snapshotStore;
+
+  @TempDir
+  private Path tempDir;
+
+  private OmSnapshotLocalDataManager localDataManager;
+  private AutoCloseable mocks;
+
+  private File snapshotsDir;
+
+  @BeforeAll
+  public static void setupClass() {
+    snapshotLocalDataYamlSerializer = new YamlSerializer<OmSnapshotLocalData>(
+        new OmSnapshotLocalDataYaml.YamlFactory()) {
+
+      @Override
+      public void computeAndSetChecksum(Yaml yaml, OmSnapshotLocalData data) 
throws IOException {
+        data.computeAndSetChecksum(yaml);
+      }
+    };
+  }
+
+  @AfterAll
+  public static void teardownClass() throws IOException {
+    snapshotLocalDataYamlSerializer.close();
+    snapshotLocalDataYamlSerializer = null;
+  }
+
+  @BeforeEach
+  public void setUp() throws IOException {
+    mocks = MockitoAnnotations.openMocks(this);
+    
+    // Setup mock behavior
+    when(omMetadataManager.getStore()).thenReturn(rdbStore);
+
+    this.snapshotsDir = tempDir.resolve("snapshots").toFile();
+    FileUtils.deleteDirectory(snapshotsDir);
+    assertTrue(snapshotsDir.exists() || snapshotsDir.mkdirs());
+    File dbLocation = tempDir.resolve("db").toFile();
+    FileUtils.deleteDirectory(dbLocation);
+    assertTrue(dbLocation.exists() || dbLocation.mkdirs());
+
+    
+    
when(rdbStore.getSnapshotsParentDir()).thenReturn(snapshotsDir.getAbsolutePath());
+    when(rdbStore.getDbLocation()).thenReturn(dbLocation);
+  }
+
+  @AfterEach
+  public void tearDown() throws Exception {
+    if (localDataManager != null) {
+      localDataManager.close();
+    }
+    if (mocks != null) {
+      mocks.close();
+    }
+  }
+
+  @Test
+  public void testConstructor() throws IOException {
+    localDataManager = new OmSnapshotLocalDataManager(omMetadataManager);
+    assertNotNull(localDataManager);
+  }
+
+  @Test
+  public void testGetSnapshotLocalPropertyYamlPathWithSnapshotInfo() throws 
IOException {
+    UUID snapshotId = UUID.randomUUID();
+    SnapshotInfo snapshotInfo = createMockSnapshotInfo(snapshotId, null);
+    
+    localDataManager = new OmSnapshotLocalDataManager(omMetadataManager);
+    
+    File yamlPath = new 
File(localDataManager.getSnapshotLocalPropertyYamlPath(snapshotInfo));
+    assertNotNull(yamlPath);
+    Path expectedYamlPath = Paths.get(snapshotsDir.getAbsolutePath(), "db" + 
OM_SNAPSHOT_SEPARATOR + snapshotId
+        + YAML_FILE_EXTENSION);
+    assertEquals(expectedYamlPath.toAbsolutePath().toString(), 
yamlPath.getAbsolutePath());
+  }
+
+  @Test
+  public void testCreateNewOmSnapshotLocalDataFile() throws IOException {
+    UUID snapshotId = UUID.randomUUID();
+    SnapshotInfo snapshotInfo = createMockSnapshotInfo(snapshotId, null);
+    
+    // Setup snapshot store mock
+    File snapshotDbLocation = 
OmSnapshotManager.getSnapshotPath(omMetadataManager, snapshotId).toFile();
+    assertTrue(snapshotDbLocation.exists() || snapshotDbLocation.mkdirs());
+
+    List<LiveFileMetaData> sstFiles = new ArrayList<>();
+    sstFiles.add(createMockLiveFileMetaData("file1.sst", KEY_TABLE, "key1", 
"key7"));
+    sstFiles.add(createMockLiveFileMetaData("file2.sst", KEY_TABLE, "key3", 
"key9"));
+    sstFiles.add(createMockLiveFileMetaData("file3.sst", FILE_TABLE, "key1", 
"key7"));
+    sstFiles.add(createMockLiveFileMetaData("file4.sst", FILE_TABLE, "key1", 
"key7"));
+    sstFiles.add(createMockLiveFileMetaData("file5.sst", DIRECTORY_TABLE, 
"key1", "key7"));
+    sstFiles.add(createMockLiveFileMetaData("file6.sst", "colFamily1", "key1", 
"key7"));
+    List<SstFileInfo> sstFileInfos = IntStream.range(0, sstFiles.size() - 1)
+        
.mapToObj(sstFiles::get).map(SstFileInfo::new).collect(Collectors.toList());
+    when(snapshotStore.getDbLocation()).thenReturn(snapshotDbLocation);
+    RocksDatabase rocksDatabase = mock(RocksDatabase.class);
+    when(snapshotStore.getDb()).thenReturn(rocksDatabase);
+    when(rocksDatabase.getLiveFilesMetaData()).thenReturn(sstFiles);
+    localDataManager = new OmSnapshotLocalDataManager(omMetadataManager);
+    
+    localDataManager.createNewOmSnapshotLocalDataFile(snapshotStore, 
snapshotInfo);
+    
+    // Verify file was created
+    OmSnapshotLocalData snapshotLocalData = 
localDataManager.getOmSnapshotLocalData(snapshotId);
+    assertEquals(1, snapshotLocalData.getVersionSstFileInfos().size());
+    OmSnapshotLocalData.VersionMeta versionMeta = 
snapshotLocalData.getVersionSstFileInfos().get(0);
+    OmSnapshotLocalData.VersionMeta expectedVersionMeta = new 
OmSnapshotLocalData.VersionMeta(0, sstFileInfos);
+    assertEquals(expectedVersionMeta, versionMeta);
+  }
+
+  @Test
+  public void testGetOmSnapshotLocalDataWithSnapshotInfo() throws IOException {
+    UUID snapshotId = UUID.randomUUID();
+    SnapshotInfo snapshotInfo = createMockSnapshotInfo(snapshotId, null);
+    
+    // Create and write snapshot local data file
+    OmSnapshotLocalData localData = createMockLocalData(snapshotId, null);
+    
+    localDataManager = new OmSnapshotLocalDataManager(omMetadataManager);
+    
+    // Write the file manually for testing
+    Path yamlPath = 
Paths.get(localDataManager.getSnapshotLocalPropertyYamlPath(snapshotInfo.getSnapshotId()));
+    writeLocalDataToFile(localData, yamlPath);
+    
+    // Test retrieval
+    OmSnapshotLocalData retrieved = 
localDataManager.getOmSnapshotLocalData(snapshotInfo);
+    
+    assertNotNull(retrieved);
+    assertEquals(snapshotId, retrieved.getSnapshotId());
+  }
+
+  @Test
+  public void testGetOmSnapshotLocalDataWithMismatchedSnapshotId() throws 
IOException {
+    UUID snapshotId = UUID.randomUUID();
+    UUID wrongSnapshotId = UUID.randomUUID();
+    
+    // Create local data with wrong snapshot ID
+    OmSnapshotLocalData localData = createMockLocalData(wrongSnapshotId, null);
+    
+    localDataManager = new OmSnapshotLocalDataManager(omMetadataManager);
+    
+    Path yamlPath = 
Paths.get(localDataManager.getSnapshotLocalPropertyYamlPath(snapshotId));
+    writeLocalDataToFile(localData, yamlPath);
+    // Should throw IOException due to mismatched IDs
+    assertThrows(IOException.class, () -> {
+      localDataManager.getOmSnapshotLocalData(snapshotId);
+    });
+  }
+
+  @Test
+  public void testGetOmSnapshotLocalDataWithFile() throws IOException {
+    UUID snapshotId = UUID.randomUUID();
+    
+    OmSnapshotLocalData localData = createMockLocalData(snapshotId, null);
+    
+    localDataManager = new OmSnapshotLocalDataManager(omMetadataManager);
+    
+    Path yamlPath = tempDir.resolve("test-snapshot.yaml");
+    writeLocalDataToFile(localData, yamlPath);
+    
+    OmSnapshotLocalData retrieved = localDataManager
+        .getOmSnapshotLocalData(yamlPath.toFile());
+    
+    assertNotNull(retrieved);
+    assertEquals(snapshotId, retrieved.getSnapshotId());
+  }
+
+  @Test
+  public void testAddVersionNodeWithDependents() throws IOException {
+    List<UUID> versionIds = Stream.of(UUID.randomUUID(), UUID.randomUUID())
+        
.sorted(Comparator.comparing(String::valueOf)).collect(Collectors.toList());
+    UUID snapshotId = versionIds.get(0);
+    UUID previousSnapshotId = versionIds.get(1);
+    localDataManager = new OmSnapshotLocalDataManager(omMetadataManager);
+    // Create snapshot directory structure and files
+    createSnapshotLocalDataFile(snapshotId, previousSnapshotId);
+    createSnapshotLocalDataFile(previousSnapshotId, null);
+    OmSnapshotLocalData localData = createMockLocalData(snapshotId, 
previousSnapshotId);
+    
+    // Should not throw exception
+    localDataManager.addVersionNodeWithDependents(localData);
+  }
+
+  @Test
+  public void testAddVersionNodeWithDependentsAlreadyExists() throws 
IOException {
+    UUID snapshotId = UUID.randomUUID();
+    
+    createSnapshotLocalDataFile(snapshotId, null);
+    
+    localDataManager = new OmSnapshotLocalDataManager(omMetadataManager);
+    
+    OmSnapshotLocalData localData = createMockLocalData(snapshotId, null);
+    
+    // First addition
+    localDataManager.addVersionNodeWithDependents(localData);
+    
+    // Second addition - should handle gracefully
+    localDataManager.addVersionNodeWithDependents(localData);
+  }
+
+  @Test
+  public void testInitWithExistingYamlFiles() throws IOException {
+    List<UUID> versionIds = Stream.of(UUID.randomUUID(), UUID.randomUUID())
+        
.sorted(Comparator.comparing(String::valueOf)).collect(Collectors.toList());
+    UUID snapshotId = versionIds.get(0);
+    UUID previousSnapshotId = versionIds.get(1);
+    
+    createSnapshotLocalDataFile(previousSnapshotId, null);
+    createSnapshotLocalDataFile(snapshotId, previousSnapshotId);
+    
+    // Initialize - should load existing files
+    localDataManager = new OmSnapshotLocalDataManager(omMetadataManager);
+    
+    assertNotNull(localDataManager);
+    Map<UUID, OmSnapshotLocalDataManager.SnapshotVersionsMeta> versionMap =
+        localDataManager.getVersionNodeMap();
+    assertEquals(2, versionMap.size());
+    assertEquals(versionMap.keySet(), new HashSet<>(versionIds));
+  }
+
+  @Test
+  public void testInitWithInvalidPathThrowsException() throws IOException {
+    UUID snapshotId = UUID.randomUUID();
+    
+    // Create a file with wrong location
+    OmSnapshotLocalData localData = createMockLocalData(snapshotId, null);
+    Path wrongPath = Paths.get(snapshotsDir.getAbsolutePath(), 
"db-wrong-name.yaml");
+    writeLocalDataToFile(localData, wrongPath);
+    
+    // Should throw IOException during init
+    assertThrows(IOException.class, () -> {
+      new OmSnapshotLocalDataManager(omMetadataManager);
+    });
+  }
+
+  @Test
+  public void testClose() throws IOException {
+    localDataManager = new OmSnapshotLocalDataManager(omMetadataManager);
+    
+    // Should not throw exception
+    localDataManager.close();
+  }
+
+  // Helper methods
+
+  private SnapshotInfo createMockSnapshotInfo(UUID snapshotId, UUID 
previousSnapshotId) {
+    SnapshotInfo.Builder builder = SnapshotInfo.newBuilder()
+        .setSnapshotId(snapshotId)
+        .setName("snapshot-" + snapshotId);
+    
+    if (previousSnapshotId != null) {
+      builder.setPathPreviousSnapshotId(previousSnapshotId);
+    }
+    
+    return builder.build();
+  }
+
+  private LiveFileMetaData createMockLiveFileMetaData(String fileName, String 
columnFamilyName, String smallestKey,
+      String largestKey) {
+    LiveFileMetaData liveFileMetaData = mock(LiveFileMetaData.class);
+    
when(liveFileMetaData.columnFamilyName()).thenReturn(StringUtils.string2Bytes(columnFamilyName));
+    when(liveFileMetaData.fileName()).thenReturn(fileName);
+    
when(liveFileMetaData.smallestKey()).thenReturn(StringUtils.string2Bytes(smallestKey));
+    
when(liveFileMetaData.largestKey()).thenReturn(StringUtils.string2Bytes(largestKey));
+    return liveFileMetaData;
+  }
+
+  private OmSnapshotLocalData createMockLocalData(UUID snapshotId, UUID 
previousSnapshotId) {
+    List<LiveFileMetaData> sstFiles = new ArrayList<>();
+    sstFiles.add(createMockLiveFileMetaData("file1.sst", "columnFamily1", 
"key1", "key7"));
+    sstFiles.add(createMockLiveFileMetaData("file2.sst", "columnFamily1", 
"key3", "key10"));
+    sstFiles.add(createMockLiveFileMetaData("file3.sst", "columnFamily2", 
"key1", "key8"));
+    sstFiles.add(createMockLiveFileMetaData("file4.sst", "columnFamily2", 
"key0", "key10"));
+    return new OmSnapshotLocalData(snapshotId, sstFiles, previousSnapshotId);
+  }
+
+  private void createSnapshotLocalDataFile(UUID snapshotId, UUID 
previousSnapshotId)
+      throws IOException {
+    OmSnapshotLocalData localData = createMockLocalData(snapshotId, 
previousSnapshotId);
+    
+    String fileName = "db" + OM_SNAPSHOT_SEPARATOR + snapshotId.toString() + 
YAML_FILE_EXTENSION;
+    Path yamlPath = Paths.get(snapshotsDir.getAbsolutePath(), fileName);
+    
+    writeLocalDataToFile(localData, yamlPath);
+  }
+
+  private void writeLocalDataToFile(OmSnapshotLocalData localData, Path 
filePath)
+      throws IOException {
+    // This is a simplified version - in real implementation, 
+    // you would use the YamlSerializer
+    snapshotLocalDataYamlSerializer.save(filePath.toFile(), localData);
+  }
+}
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/filter/AbstractReclaimableFilterTest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/filter/AbstractReclaimableFilterTest.java
index e8c362d9a5f..13ba79a77f8 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/filter/AbstractReclaimableFilterTest.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/filter/AbstractReclaimableFilterTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
 import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.when;
 
@@ -61,6 +62,7 @@
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
 import org.apache.hadoop.ozone.om.lock.IOzoneManagerLock;
 import org.apache.hadoop.ozone.om.lock.OMLockDetails;
+import org.apache.hadoop.ozone.om.snapshot.OmSnapshotLocalDataManager;
 import org.apache.hadoop.ozone.om.snapshot.SnapshotCache;
 import org.apache.hadoop.ozone.om.snapshot.SnapshotDiffManager;
 import org.apache.hadoop.ozone.om.snapshot.SnapshotUtils;
@@ -188,9 +190,9 @@ private void mockOzoneManager(BucketLayout bucketLayout) 
throws IOException {
   private void mockOmSnapshotManager(OzoneManager om) throws RocksDBException, 
IOException {
     try (MockedStatic<ManagedRocksDB> rocksdb = 
Mockito.mockStatic(ManagedRocksDB.class);
          MockedConstruction<SnapshotDiffManager> mockedSnapshotDiffManager =
-             Mockito.mockConstruction(SnapshotDiffManager.class, (mock, 
context) ->
+             mockConstruction(SnapshotDiffManager.class, (mock, context) ->
                  doNothing().when(mock).close());
-         MockedConstruction<SnapshotCache> mockedCache = 
Mockito.mockConstruction(SnapshotCache.class,
+         MockedConstruction<SnapshotCache> mockedCache = 
mockConstruction(SnapshotCache.class,
              (mock, context) -> {
                Map<UUID, UncheckedAutoCloseableSupplier<OmSnapshot>> map = new 
HashMap<>();
                when(mock.get(any(UUID.class))).thenAnswer(i -> {
@@ -237,7 +239,10 @@ private void mockOmSnapshotManager(OzoneManager om) throws 
RocksDBException, IOE
       conf.set(OZONE_METADATA_DIRS, 
testDir.toAbsolutePath().toFile().getAbsolutePath());
       when(om.getConfiguration()).thenReturn(conf);
       when(om.isFilesystemSnapshotEnabled()).thenReturn(true);
-      this.omSnapshotManager = new OmSnapshotManager(om);
+      try (MockedConstruction<OmSnapshotLocalDataManager> ignored =
+               mockConstruction(OmSnapshotLocalDataManager.class)) {
+        this.omSnapshotManager = new OmSnapshotManager(om);
+      }
     }
   }
 


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


Reply via email to