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

sshenoy pushed a commit to branch HDDS-6517-Snapshot
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/HDDS-6517-Snapshot by this 
push:
     new 52103f273a HDDS-6984. [Snapshot] Prevent bucket from being deleted if 
snapshot exists (#4015)
52103f273a is described below

commit 52103f273a186b1b3f1b0390e2363af3d45c2618
Author: Sadanand Shenoy <[email protected]>
AuthorDate: Wed Jan 18 00:57:21 2023 +0530

    HDDS-6984. [Snapshot] Prevent bucket from being deleted if snapshot exists 
(#4015)
---
 .../hadoop/ozone/om/exceptions/OMException.java    |  4 +-
 .../org/apache/hadoop/ozone/om/TestOmSnapshot.java | 27 +++++++++++
 .../src/main/proto/OmClientProtocol.proto          |  2 +
 .../om/request/bucket/OMBucketDeleteRequest.java   | 56 ++++++++++++++++++++++
 4 files changed, 88 insertions(+), 1 deletion(-)

diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/exceptions/OMException.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/exceptions/OMException.java
index 4b4a1698ec..fb920583b8 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/exceptions/OMException.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/exceptions/OMException.java
@@ -259,6 +259,8 @@ public class OMException extends IOException {
 
     FEATURE_NOT_ENABLED,
 
-    INVALID_SNAPSHOT_ERROR
+    INVALID_SNAPSHOT_ERROR,
+
+    CONTAINS_SNAPSHOT,
   }
 }
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshot.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshot.java
index 719b07c222..ee5ca09866 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshot.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmSnapshot.java
@@ -46,6 +46,7 @@ import org.junit.Assert;
 import org.junit.AfterClass;
 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;
@@ -65,6 +66,7 @@ import java.util.concurrent.TimeoutException;
 import static org.apache.hadoop.ozone.OzoneConsts.OM_DB_NAME;
 import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX;
 import static org.apache.hadoop.ozone.OzoneConsts.OM_SNAPSHOT_DIR;
+import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.CONTAINS_SNAPSHOT;
 import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.KEY_NOT_FOUND;
 import static 
org.apache.hadoop.ozone.om.helpers.BucketLayout.FILE_SYSTEM_OPTIMIZED;
 import static org.apache.hadoop.ozone.om.helpers.BucketLayout.OBJECT_STORE;
@@ -399,6 +401,31 @@ public class TestOmSnapshot {
             () -> createSnapshot(volume, bucket));
   }
 
+  @Test
+  public void testBucketDeleteIfSnapshotExists() throws Exception {
+    String volume1 = "vol-" + RandomStringUtils.randomNumeric(5);
+    String bucket1 = "buc-" + RandomStringUtils.randomNumeric(5);
+    String bucket2 = "buc-" + RandomStringUtils.randomNumeric(5);
+    store.createVolume(volume1);
+    OzoneVolume volume = store.getVolume(volume1);
+    volume.createBucket(bucket1);
+    volume.createBucket(bucket2);
+    OzoneBucket bucketWithSnapshot = volume.getBucket(bucket1);
+    OzoneBucket bucketWithoutSnapshot = volume.getBucket(bucket2);
+    String key = "key-";
+    createFileKey(bucketWithSnapshot, key);
+    createFileKey(bucketWithoutSnapshot, key);
+    createSnapshot(volume1, bucket1);
+    deleteKeys(bucketWithSnapshot);
+    deleteKeys(bucketWithoutSnapshot);
+    OMException omException = Assertions.assertThrows(OMException.class,
+        () -> volume.deleteBucket(bucket1));
+    Assertions.assertEquals(CONTAINS_SNAPSHOT, omException.getResult());
+    // TODO: Delete snapshot then delete bucket1 when deletion is implemented
+    // no exception for bucket without snapshot
+    volume.deleteBucket(bucket2);
+  }
+
   @Test
   public void testSnapDiff() throws Exception {
     String volume = "vol-" + RandomStringUtils.randomNumeric(5);
diff --git 
a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto 
b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index 2aff916854..b810a41bb0 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -457,6 +457,8 @@ enum Status {
     FEATURE_NOT_ENABLED = 86;
 
     INVALID_SNAPSHOT_ERROR = 87;
+
+    CONTAINS_SNAPSHOT = 88;
 }
 
 /**
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketDeleteRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketDeleteRequest.java
index 12dfd4d3ca..ec6cb3b86a 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketDeleteRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketDeleteRequest.java
@@ -19,12 +19,16 @@
 package org.apache.hadoop.ozone.om.request.bucket;
 
 import java.io.IOException;
+import java.util.Iterator;
 import java.util.Map;
 
 import com.google.common.base.Optional;
+import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.hdds.utils.db.TableIterator;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
 import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
 import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
+import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
 import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper;
 import org.apache.hadoop.ozone.om.request.util.OmResponseUtil;
 import org.apache.hadoop.ozone.om.request.validation.RequestFeatureValidator;
@@ -60,6 +64,7 @@ import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
 
 import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.BUCKET_NOT_FOUND;
+import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.CONTAINS_SNAPSHOT;
 import static 
org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.BUCKET_LOCK;
 import static 
org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.VOLUME_LOCK;
 
@@ -135,6 +140,19 @@ public class OMBucketDeleteRequest extends OMClientRequest 
{
         throw new OMException("Bucket is not empty",
             OMException.ResultCodes.BUCKET_NOT_EMPTY);
       }
+
+      // appending '/' to end to eliminate cases where 2 buckets start with 
same
+      // characters.
+      String snapshotBucketKey = bucketKey + OzoneConsts.OM_KEY_PREFIX;
+
+      if (bucketContainsSnapshot(omMetadataManager, snapshotBucketKey)) {
+        LOG.debug("Bucket '{}' can't be deleted when it has snapshots",
+            bucketName);
+        throw new OMException(
+            "Bucket " + bucketName + " can't be deleted when it has snapshots",
+            CONTAINS_SNAPSHOT);
+      }
+
       if (omBucketInfo.getBucketLayout().isFileSystemOptimized()) {
         omMetrics.incNumFSOBucketDeletes();
       }
@@ -198,6 +216,44 @@ public class OMBucketDeleteRequest extends OMClientRequest 
{
     }
   }
 
+  private boolean bucketContainsSnapshot(OMMetadataManager omMetadataManager,
+      String snapshotBucketKey) throws IOException {
+    return bucketContainsSnapshotInCache(omMetadataManager, snapshotBucketKey)
+        || bucketContainsSnapshotInTable(omMetadataManager, snapshotBucketKey);
+  }
+
+  private boolean bucketContainsSnapshotInTable(
+      OMMetadataManager omMetadataManager, String snapshotBucketKey)
+      throws IOException {
+    try (
+        TableIterator<String, ? extends Table.KeyValue<String, SnapshotInfo>>
+            snapshotIterator = omMetadataManager
+            .getSnapshotInfoTable().iterator()) {
+      snapshotIterator.seek(snapshotBucketKey);
+      if (snapshotIterator.hasNext()) {
+        return snapshotIterator.next().getKey().startsWith(snapshotBucketKey);
+      }
+    }
+    return false;
+  }
+
+  private boolean bucketContainsSnapshotInCache(
+      OMMetadataManager omMetadataManager, String snapshotBucketKey) {
+    Iterator<Map.Entry<CacheKey<String>, CacheValue<SnapshotInfo>>> cacheIter =
+        omMetadataManager.getSnapshotInfoTable().cacheIterator();
+    while (cacheIter.hasNext()) {
+      Map.Entry<CacheKey<String>, CacheValue<SnapshotInfo>> cacheKeyValue =
+          cacheIter.next();
+      String key = cacheKeyValue.getKey().getCacheKey();
+      // TODO: Revisit when delete snapshot gets implemented as entry
+      //  in cache/table could be null.
+      if (key.startsWith(snapshotBucketKey)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   /**
    * Validates bucket delete requests.
    * Handles the cases where an older client attempts to delete a bucket


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

Reply via email to