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

siyao 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 e8e34c2a91 HDDS-7914. [Snapshot] Block FS API access to deleted 
(non-active) snapshots (#4419)
e8e34c2a91 is described below

commit e8e34c2a9170462adff57611caa09cfda55fc8cc
Author: Siyao Meng <[email protected]>
AuthorDate: Wed Mar 29 15:26:22 2023 -0700

    HDDS-7914. [Snapshot] Block FS API access to deleted (non-active) snapshots 
(#4419)
---
 .../hadoop/ozone/om/TestOmSnapshotFileSystem.java  | 70 +++++++++++++++++++++-
 .../org/apache/hadoop/ozone/om/OmSnapshot.java     |  2 +-
 .../apache/hadoop/ozone/om/OmSnapshotManager.java  | 52 ++++++++++++++++
 .../request/snapshot/OMSnapshotDeleteRequest.java  |  5 ++
 .../hadoop/ozone/om/TestOmSnapshotManager.java     |  3 +-
 .../snapshot/TestOMSnapshotDeleteRequest.java      |  7 +++
 6 files changed, 134 insertions(+), 5 deletions(-)

diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshotFileSystem.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshotFileSystem.java
index 1e822d6159..ba5c82bdc3 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshotFileSystem.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshotFileSystem.java
@@ -39,16 +39,19 @@ import org.apache.hadoop.ozone.client.OzoneKey;
 import org.apache.hadoop.ozone.client.OzoneVolume;
 import org.apache.hadoop.ozone.client.io.OzoneInputStream;
 import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
 import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
 import org.apache.hadoop.ozone.om.helpers.OpenKeySession;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
 import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
 import org.apache.ozone.test.GenericTestUtils;
+import org.apache.ozone.test.LambdaTestUtils;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
 import org.junit.rules.Timeout;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -134,11 +137,13 @@ public class TestOmSnapshotFileSystem {
       init();
     }
   }
+
   private static void setConfig(BucketLayout newBucketLayout,
       boolean newEnableFileSystemPaths) {
     TestOmSnapshotFileSystem.enabledFileSystemPaths = newEnableFileSystemPaths;
     TestOmSnapshotFileSystem.bucketLayout = newBucketLayout;
   }
+
   /**
    * Create a MiniDFSCluster for testing.
    */
@@ -367,6 +372,60 @@ public class TestOmSnapshotFileSystem {
     keyPrefix = s;
   }
 
+  @Test
+  public void testBlockSnapshotFSAccessAfterDeletion() throws Exception {
+    Path root = new Path("/");
+    Path dir = new Path(root, "/testListKeysBeforeAfterSnapshotDeletion");
+    Path key1 = new Path(dir, "key1");
+    Path key2 = new Path(dir, "key2");
+
+    // Create 2 keys
+    ContractTestUtils.touch(fs, key1);
+    ContractTestUtils.touch(fs, key2);
+
+    // Create a snapshot
+    String snapshotName = UUID.randomUUID().toString();
+    String snapshotKeyPrefix = createSnapshot(snapshotName);
+
+    // Can list keys in snapshot
+    Path snapshotRoot = new Path(snapshotKeyPrefix + root);
+    Path snapshotParent = new Path(snapshotKeyPrefix + dir);
+    // Check dir in snapshot
+    FileStatus[] fileStatuses = o3fs.listStatus(snapshotRoot);
+    Assertions.assertEquals(1, fileStatuses.length);
+    // List keys in dir in snapshot
+    fileStatuses = o3fs.listStatus(snapshotParent);
+    Assertions.assertEquals(2, fileStatuses.length);
+
+    // Check key metadata
+    Path snapshotKey1 = new Path(snapshotKeyPrefix + key1);
+    FileStatus fsActiveKey = o3fs.getFileStatus(key1);
+    FileStatus fsSnapshotKey = o3fs.getFileStatus(snapshotKey1);
+    Assert.assertEquals(fsActiveKey.getModificationTime(),
+        fsSnapshotKey.getModificationTime());
+
+    Path snapshotKey2 = new Path(snapshotKeyPrefix + key2);
+    fsActiveKey = o3fs.getFileStatus(key2);
+    fsSnapshotKey = o3fs.getFileStatus(snapshotKey2);
+    Assert.assertEquals(fsActiveKey.getModificationTime(),
+        fsSnapshotKey.getModificationTime());
+
+    // Delete the snapshot
+    deleteSnapshot(snapshotName);
+
+    // Can't access keys in snapshot anymore with FS API. Should throw 
exception
+    final String errorMsg = "no longer active";
+    LambdaTestUtils.intercept(OMException.class, errorMsg,
+        () -> o3fs.listStatus(snapshotRoot));
+    LambdaTestUtils.intercept(OMException.class, errorMsg,
+        () -> o3fs.listStatus(snapshotParent));
+
+    LambdaTestUtils.intercept(OMException.class, errorMsg,
+        () -> o3fs.getFileStatus(snapshotKey1));
+    LambdaTestUtils.intercept(OMException.class, errorMsg,
+        () -> o3fs.getFileStatus(snapshotKey2));
+  }
+
   @Test
   // based on TestOzoneFileSystem:testListStatus
   public void testListStatus() throws Exception {
@@ -524,7 +583,6 @@ public class TestOmSnapshotFileSystem {
     writeClient.commitKey(keyArgs, session.getId());
   }
 
-
   /**
    * Tests listStatus operation on root directory.
    */
@@ -637,9 +695,13 @@ public class TestOmSnapshotFileSystem {
 
   private String createSnapshot()
       throws IOException, InterruptedException, TimeoutException {
+    return createSnapshot(UUID.randomUUID().toString());
+  }
+
+  private String createSnapshot(String snapshotName)
+      throws IOException, InterruptedException, TimeoutException {
 
     // create snapshot
-    String snapshotName = UUID.randomUUID().toString();
     writeClient.createSnapshot(volumeName, bucketName, snapshotName);
 
     // wait till the snapshot directory exists
@@ -654,4 +716,8 @@ public class TestOmSnapshotFileSystem {
 
     return OM_KEY_PREFIX + OmSnapshotManager.getSnapshotPrefix(snapshotName);
   }
+
+  private void deleteSnapshot(String snapshotName) throws IOException {
+    writeClient.deleteSnapshot(volumeName, bucketName, snapshotName);
+  }
 }
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshot.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshot.java
index 3f3935e028..2a02e940a4 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshot.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmSnapshot.java
@@ -67,6 +67,7 @@ public class OmSnapshot implements IOmMetadataReader, 
Closeable {
   private final String volumeName;
   private final String bucketName;
   private final String snapshotName;
+  // To access snapshot checkpoint DB metadata
   private final OMMetadataManager omMetadataManager;
 
   public OmSnapshot(KeyManager keyManager,
@@ -84,7 +85,6 @@ public class OmSnapshot implements IOmMetadataReader, 
Closeable {
     this.omMetadataManager = keyManager.getMetadataManager();
   }
 
-
   @Override
   public OmKeyInfo lookupKey(OmKeyArgs args) throws IOException {
     return denormalizeOmKeyInfo(omMetadataReader.lookupKey(
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 e05dc897dd..91fc4498c9 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
@@ -51,6 +51,7 @@ import org.apache.hadoop.ozone.om.exceptions.OMException;
 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.om.service.SnapshotDeletingService;
 import org.apache.hadoop.ozone.om.snapshot.SnapshotDiffManager;
 import org.apache.hadoop.ozone.om.snapshot.SnapshotUtils;
 import org.apache.hadoop.ozone.snapshot.SnapshotDiffResponse;
@@ -69,6 +70,7 @@ import static 
org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX;
 import static org.apache.hadoop.ozone.OzoneConsts.OM_SNAPSHOT_DIFF_DB_NAME;
 import static org.apache.hadoop.ozone.OzoneConsts.OM_SNAPSHOT_INDICATOR;
 import static 
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SNAPSHOT_DIFF_DB_DIR;
+import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.FILE_NOT_FOUND;
 import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_KEY_NAME;
 
 /**
@@ -174,6 +176,28 @@ public final class OmSnapshotManager implements 
AutoCloseable {
         // see if the snapshot exists
         snapshotInfo = getSnapshotInfo(snapshotTableKey);
 
+        // Block snapshot from loading when it is no longer active e.g. 
DELETED,
+        // unless this is called from SnapshotDeletingService.
+        // TODO: [SNAPSHOT] However, snapshotCache.get() from other requests
+        //  (not from SDS) would be able to piggyback off of this because
+        //  snapshot still in cache won't trigger loader again.
+        //  This needs proper addressal in e.g. HDDS-7935
+        //  by introducing another cache just for SDS.
+        //  While the snapshotCache would host ACTIVE snapshots only.
+        if (!snapshotInfo.getSnapshotStatus().equals(
+                SnapshotStatus.SNAPSHOT_ACTIVE)) {
+          if (isCalledFromSnapshotDeletingService()) {
+            LOG.debug("Permitting {} to load snapshot {} in status: {}",
+                SnapshotDeletingService.class.getSimpleName(),
+                snapshotInfo.getTableKey(),
+                snapshotInfo.getSnapshotStatus());
+          } else {
+            throw new OMException("Unable to load snapshot. " +
+                "Snapshot with table key '" + snapshotTableKey +
+                "' is no longer active", FILE_NOT_FOUND);
+          }
+        }
+
         CacheValue<SnapshotInfo> cacheValue =
             ozoneManager.getMetadataManager().getSnapshotInfoTable()
                 .getCacheValue(new CacheKey<>(snapshotTableKey));
@@ -214,6 +238,8 @@ public final class OmSnapshotManager implements 
AutoCloseable {
         = notification -> {
           try {
             // close snapshot's rocksdb on eviction
+            LOG.debug("Closing snapshot: {}", notification.getKey());
+            // TODO: [SNAPSHOT] HDDS-7935.Close only when refcount reaches 
zero?
             notification.getValue().close();
           } catch (IOException e) {
             LOG.error("Failed to close snapshot: {} {}",
@@ -232,6 +258,32 @@ public final class OmSnapshotManager implements 
AutoCloseable {
         columnFamilyOptions);
   }
 
+  /**
+   * Helper method to check whether the loader is called from
+   * SnapshotDeletingTask (return true) or not (return false).
+   */
+  private boolean isCalledFromSnapshotDeletingService() {
+
+    StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+    for (StackTraceElement elem : stackTrace) {
+      // Allow as long as loader is called from SDS. e.g. SnapshotDeletingTask
+      if (elem.getClassName().startsWith(
+          SnapshotDeletingService.class.getName())) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Get snapshot instance LRU cache.
+   * @return LoadingCache
+   */
+  public LoadingCache<String, OmSnapshot> getSnapshotCache() {
+    return snapshotCache;
+  }
+
   /**
    * Creates snapshot checkpoint that corresponds to snapshotInfo.
    * @param omMetadataManager the metadata manager
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotDeleteRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotDeleteRequest.java
index b2684d9cd1..7af3f1486f 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotDeleteRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotDeleteRequest.java
@@ -184,6 +184,11 @@ public class OMSnapshotDeleteRequest extends 
OMClientRequest {
       omClientResponse = new OMSnapshotDeleteResponse(
           omResponse.build(), tableKey, snapshotInfo);
 
+      // Evict the snapshot entry from cache, and close the snapshot DB
+      // Nothing happens if the key doesn't exist in cache (snapshot not 
loaded)
+      ozoneManager.getOmSnapshotManager().getSnapshotCache()
+          .invalidate(tableKey);
+
     } catch (IOException ex) {
       exception = ex;
       omClientResponse = new OMSnapshotDeleteResponse(
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshotManager.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshotManager.java
index 7da918de1f..534524c778 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshotManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshotManager.java
@@ -97,7 +97,7 @@ public class TestOmSnapshotManager {
     HddsWhiteboxTestUtils.setInternalState(
         firstSnapshot.getMetadataManager(), "store", firstSnapshotStore);
 
-    //  create second snapshot checkpoint (which will be used for eviction)
+    // create second snapshot checkpoint (which will be used for eviction)
     OmSnapshotManager.createOmSnapshotCheckpoint(om.getMetadataManager(),
         second);
 
@@ -109,7 +109,6 @@ public class TestOmSnapshotManager {
         .checkForSnapshot(second.getVolumeName(),
         second.getBucketName(), getSnapshotPrefix(second.getName()));
 
-
     // confirm store was closed
     verify(firstSnapshotStore, timeout(3000).times(1)).close();
   }
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 c872312c1b..0012e9494f 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
@@ -20,6 +20,7 @@
 
 package org.apache.hadoop.ozone.om.request.snapshot;
 
+import com.google.common.cache.LoadingCache;
 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;
@@ -28,6 +29,7 @@ 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;
@@ -98,6 +100,11 @@ public class TestOMSnapshotDeleteRequest {
     when(ozoneManager.getAuditLogger()).thenReturn(auditLogger);
     Mockito.doNothing().when(auditLogger).logWrite(any(AuditMessage.class));
 
+    OmSnapshotManager omSnapshotManager = mock(OmSnapshotManager.class);
+    when(omSnapshotManager.getSnapshotCache())
+        .thenReturn(mock(LoadingCache.class));
+    when(ozoneManager.getOmSnapshotManager()).thenReturn(omSnapshotManager);
+
     volumeName = UUID.randomUUID().toString();
     bucketName = UUID.randomUUID().toString();
     snapshotName = UUID.randomUUID().toString();


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

Reply via email to