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 0f52a34d0c HDDS-12559. Implement Bulk Ozone Locks for taking locks on 
multiple snapshots (#8052)
0f52a34d0c is described below

commit 0f52a34d0c45f5f17a568ea0d183880dfcdcd063
Author: Swaminathan Balachandran <[email protected]>
AuthorDate: Wed Apr 2 14:31:19 2025 -0700

    HDDS-12559. Implement Bulk Ozone Locks for taking locks on multiple 
snapshots (#8052)
---
 .../hadoop/ozone/om/lock/IOzoneManagerLock.java    |  13 ++
 .../hadoop/ozone/om/lock/OmReadOnlyLock.java       |  21 +++
 .../hadoop/ozone/om/lock/OzoneManagerLock.java     | 147 ++++++++++++++++++++-
 .../hadoop/ozone/om/lock/TestOzoneManagerLock.java |  32 +++++
 .../ozone/om/snapshot/MultiSnapshotLocks.java      |  85 ++++++++++++
 .../ozone/om/snapshot/TestMultiSnapshotLocks.java  | 136 +++++++++++++++++++
 6 files changed, 427 insertions(+), 7 deletions(-)

diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/IOzoneManagerLock.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/IOzoneManagerLock.java
index fac864b213..6926b7d9bf 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/IOzoneManagerLock.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/IOzoneManagerLock.java
@@ -18,6 +18,7 @@
 package org.apache.hadoop.ozone.om.lock;
 
 import com.google.common.annotations.VisibleForTesting;
+import java.util.Collection;
 
 /**
  * Interface for OM Metadata locks.
@@ -27,9 +28,15 @@ public interface IOzoneManagerLock {
   OMLockDetails acquireReadLock(OzoneManagerLock.Resource resource,
                                 String... resources);
 
+  OMLockDetails acquireReadLocks(OzoneManagerLock.Resource resource, 
Collection<String[]> resources);
+
+
   OMLockDetails acquireWriteLock(OzoneManagerLock.Resource resource,
                                  String... resources);
 
+  OMLockDetails acquireWriteLocks(OzoneManagerLock.Resource resource,
+                                 Collection<String[]> resources);
+
   boolean acquireMultiUserLock(String firstUser, String secondUser);
 
   void releaseMultiUserLock(String firstUser, String secondUser);
@@ -37,9 +44,15 @@ OMLockDetails acquireWriteLock(OzoneManagerLock.Resource 
resource,
   OMLockDetails releaseWriteLock(OzoneManagerLock.Resource resource,
                         String... resources);
 
+  OMLockDetails releaseWriteLocks(OzoneManagerLock.Resource resource,
+                                 Collection<String[]> resources);
+
   OMLockDetails releaseReadLock(OzoneManagerLock.Resource resource,
                                 String... resources);
 
+  OMLockDetails releaseReadLocks(OzoneManagerLock.Resource resource,
+                                Collection<String[]> resources);
+
   @VisibleForTesting
   int getReadHoldCount(OzoneManagerLock.Resource resource,
       String... resources);
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/OmReadOnlyLock.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/OmReadOnlyLock.java
index b1b4296cba..059536fe0a 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/OmReadOnlyLock.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/OmReadOnlyLock.java
@@ -20,6 +20,7 @@
 import static 
org.apache.hadoop.ozone.om.lock.OMLockDetails.EMPTY_DETAILS_LOCK_ACQUIRED;
 import static 
org.apache.hadoop.ozone.om.lock.OMLockDetails.EMPTY_DETAILS_LOCK_NOT_ACQUIRED;
 
+import java.util.Collection;
 import org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource;
 
 /**
@@ -34,12 +35,22 @@ public OMLockDetails acquireReadLock(Resource resource, 
String... resources) {
     return EMPTY_DETAILS_LOCK_ACQUIRED;
   }
 
+  @Override
+  public OMLockDetails acquireReadLocks(Resource resource, 
Collection<String[]> resources) {
+    return EMPTY_DETAILS_LOCK_ACQUIRED;
+  }
+
   @Override
   public OMLockDetails acquireWriteLock(Resource resource,
       String... resources) {
     return EMPTY_DETAILS_LOCK_NOT_ACQUIRED;
   }
 
+  @Override
+  public OMLockDetails acquireWriteLocks(Resource resource, 
Collection<String[]> resources) {
+    return EMPTY_DETAILS_LOCK_NOT_ACQUIRED;
+  }
+
   @Override
   public boolean acquireMultiUserLock(String firstUser, String secondUser) {
     return false;
@@ -56,11 +67,21 @@ public OMLockDetails releaseWriteLock(Resource resource,
     return EMPTY_DETAILS_LOCK_NOT_ACQUIRED;
   }
 
+  @Override
+  public OMLockDetails releaseWriteLocks(Resource resource, 
Collection<String[]> resources) {
+    return EMPTY_DETAILS_LOCK_NOT_ACQUIRED;
+  }
+
   @Override
   public OMLockDetails releaseReadLock(Resource resource, String... resources) 
{
     return EMPTY_DETAILS_LOCK_NOT_ACQUIRED;
   }
 
+  @Override
+  public OMLockDetails releaseReadLocks(Resource resource, 
Collection<String[]> resources) {
+    return EMPTY_DETAILS_LOCK_NOT_ACQUIRED;
+  }
+
   @Override
   public int getReadHoldCount(Resource resource, String... resources) {
     return 0;
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/OzoneManagerLock.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/OzoneManagerLock.java
index ab33a4f0c2..9fd567344c 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/OzoneManagerLock.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/lock/OzoneManagerLock.java
@@ -27,14 +27,19 @@
 import com.google.common.util.concurrent.Striped;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 import org.apache.hadoop.hdds.conf.ConfigurationSource;
+import org.apache.hadoop.hdds.utils.CompositeKey;
 import org.apache.hadoop.hdds.utils.SimpleStriped;
 import org.apache.hadoop.ipc.ProcessingDetails.Timing;
 import org.apache.hadoop.ipc.Server;
@@ -122,6 +127,17 @@ private Striped<ReadWriteLock> createStripeLock(Resource r,
     return SimpleStriped.readWriteLock(size, fair);
   }
 
+  private Iterable<ReadWriteLock> bulkGetLock(Resource resource, 
Collection<String[]> keys) {
+    Striped<ReadWriteLock> striped = stripedLockByResource.get(resource);
+    List<Object> lockKeys = new ArrayList<>(keys.size());
+    for (String[] key : keys) {
+      if (Objects.nonNull(key)) {
+        lockKeys.add(CompositeKey.combineKeys(key));
+      }
+    }
+    return striped.bulkGet(lockKeys);
+  }
+
   private ReentrantReadWriteLock getLock(Resource resource, String... keys) {
     Striped<ReadWriteLock> striped = stripedLockByResource.get(resource);
     Object key = combineKeys(keys);
@@ -150,6 +166,28 @@ public OMLockDetails acquireReadLock(Resource resource, 
String... keys) {
     return acquireLock(resource, true, keys);
   }
 
+  /**
+   * Acquire read locks on a list of resources.
+   *
+   * For S3_BUCKET_LOCK, VOLUME_LOCK, BUCKET_LOCK type resource, same
+   * thread acquiring lock again is allowed.
+   *
+   * For USER_LOCK, PREFIX_LOCK, S3_SECRET_LOCK type resource, same thread
+   * acquiring lock again is not allowed.
+   *
+   * Special Note for USER_LOCK: Single thread can acquire single user lock/
+   * multi user lock. But not both at the same time.
+   * @param resource - Type of the resource.
+   * @param keys - A list of Resource names on which user want to acquire 
locks.
+   * For Resource type BUCKET_LOCK, first param should be volume, second param
+   * should be bucket name. For remaining all resource only one param should
+   * be passed.
+   */
+  @Override
+  public OMLockDetails acquireReadLocks(Resource resource, 
Collection<String[]> keys) {
+    return acquireLocks(resource, true, keys);
+  }
+
   /**
    * Acquire write lock on resource.
    *
@@ -172,6 +210,59 @@ public OMLockDetails acquireWriteLock(Resource resource, 
String... keys) {
     return acquireLock(resource, false, keys);
   }
 
+  /**
+   * Acquire write locks on a list of resources.
+   *
+   * For S3_BUCKET_LOCK, VOLUME_LOCK, BUCKET_LOCK type resource, same
+   * thread acquiring lock again is allowed.
+   *
+   * For USER_LOCK, PREFIX_LOCK, S3_SECRET_LOCK type resource, same thread
+   * acquiring lock again is not allowed.
+   *
+   * Special Note for USER_LOCK: Single thread can acquire single user lock/
+   * multi user lock. But not both at the same time.
+   * @param resource - Type of the resource.
+   * @param keys - A list of Resource names on which user want to acquire lock.
+   * For Resource type BUCKET_LOCK, first param should be volume, second param
+   * should be bucket name. For remaining all resource only one param should
+   * be passed.
+   */
+  @Override
+  public OMLockDetails acquireWriteLocks(Resource resource, 
Collection<String[]> keys) {
+    return acquireLocks(resource, false, keys);
+  }
+
+  private void acquireLock(Resource resource, boolean isReadLock, 
ReadWriteLock lock,
+                           long startWaitingTimeNanos) {
+    if (isReadLock) {
+      lock.readLock().lock();
+      updateReadLockMetrics(resource, (ReentrantReadWriteLock) lock, 
startWaitingTimeNanos);
+    } else {
+      lock.writeLock().lock();
+      updateWriteLockMetrics(resource, (ReentrantReadWriteLock) lock, 
startWaitingTimeNanos);
+    }
+  }
+
+  private OMLockDetails acquireLocks(Resource resource, boolean isReadLock,
+                                    Collection<String[]> keys) {
+    omLockDetails.get().clear();
+    if (!resource.canLock(lockSet.get())) {
+      String errorMessage = getErrorMessage(resource);
+      LOG.error(errorMessage);
+      throw new RuntimeException(errorMessage);
+    }
+
+    long startWaitingTimeNanos = Time.monotonicNowNanos();
+
+    for (ReadWriteLock lock : bulkGetLock(resource, keys)) {
+      acquireLock(resource, isReadLock, lock, startWaitingTimeNanos);
+    }
+
+    lockSet.set(resource.setLock(lockSet.get()));
+    omLockDetails.get().setLockAcquired(true);
+    return omLockDetails.get();
+  }
+
   private OMLockDetails acquireLock(Resource resource, boolean isReadLock,
       String... keys) {
     omLockDetails.get().clear();
@@ -184,13 +275,7 @@ private OMLockDetails acquireLock(Resource resource, 
boolean isReadLock,
     long startWaitingTimeNanos = Time.monotonicNowNanos();
 
     ReentrantReadWriteLock lock = getLock(resource, keys);
-    if (isReadLock) {
-      lock.readLock().lock();
-      updateReadLockMetrics(resource, lock, startWaitingTimeNanos);
-    } else {
-      lock.writeLock().lock();
-      updateWriteLockMetrics(resource, lock, startWaitingTimeNanos);
-    }
+    acquireLock(resource, isReadLock, lock, startWaitingTimeNanos);
 
     lockSet.set(resource.setLock(lockSet.get()));
     omLockDetails.get().setLockAcquired(true);
@@ -317,6 +402,19 @@ public OMLockDetails releaseWriteLock(Resource resource, 
String... keys) {
     return releaseLock(resource, false, keys);
   }
 
+  /**
+   * Release write lock on multiple resources.
+   * @param resource - Type of the resource.
+   * @param keys - List of resource names on which user want to acquire lock.
+   * For Resource type BUCKET_LOCK, first param should be volume, second param
+   * should be bucket name. For remaining all resource only one param should
+   * be passed.
+   */
+  @Override
+  public OMLockDetails releaseWriteLocks(Resource resource, 
Collection<String[]> keys) {
+    return releaseLocks(resource, false, keys);
+  }
+
   /**
    * Release read lock on resource.
    * @param resource - Type of the resource.
@@ -330,6 +428,19 @@ public OMLockDetails releaseReadLock(Resource resource, 
String... keys) {
     return releaseLock(resource, true, keys);
   }
 
+  /**
+   * Release read locks on a list of resources.
+   * @param resource - Type of the resource.
+   * @param keys - Resource names on which user want to acquire lock.
+   * For Resource type BUCKET_LOCK, first param should be volume, second param
+   * should be bucket name. For remaining all resource only one param should
+   * be passed.
+   */
+  @Override
+  public OMLockDetails releaseReadLocks(Resource resource, 
Collection<String[]> keys) {
+    return releaseLocks(resource, true, keys);
+  }
+
   private OMLockDetails releaseLock(Resource resource, boolean isReadLock,
       String... keys) {
     omLockDetails.get().clear();
@@ -347,6 +458,28 @@ private OMLockDetails releaseLock(Resource resource, 
boolean isReadLock,
     return omLockDetails.get();
   }
 
+  private OMLockDetails releaseLocks(Resource resource, boolean isReadLock,
+                                    Collection<String[]> keys) {
+    omLockDetails.get().clear();
+    List<ReadWriteLock> locks =
+        StreamSupport.stream(bulkGetLock(resource, keys).spliterator(), 
false).collect(Collectors.toList());
+    // Release locks in reverse order.
+    Collections.reverse(locks);
+    for (ReadWriteLock lock : locks) {
+      if (isReadLock) {
+        lock.readLock().unlock();
+        updateReadUnlockMetrics(resource, (ReentrantReadWriteLock) lock);
+      } else {
+        boolean isWriteLocked = 
((ReentrantReadWriteLock)lock).isWriteLockedByCurrentThread();
+        lock.writeLock().unlock();
+        updateWriteUnlockMetrics(resource, (ReentrantReadWriteLock) lock, 
isWriteLocked);
+      }
+    }
+
+    lockSet.set(resource.clearLock(lockSet.get()));
+    return omLockDetails.get();
+  }
+
   private void updateReadUnlockMetrics(Resource resource,
       ReentrantReadWriteLock lock) {
     /*
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/lock/TestOzoneManagerLock.java
 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/lock/TestOzoneManagerLock.java
index 4cd44cba4b..ca986a8463 100644
--- 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/lock/TestOzoneManagerLock.java
+++ 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/lock/TestOzoneManagerLock.java
@@ -25,6 +25,7 @@
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Stack;
 import java.util.UUID;
@@ -287,6 +288,37 @@ void testLockResourceParallel() throws Exception {
 
   }
 
+  @Test
+  void testMultiLocksResourceParallel() throws Exception {
+    OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
+
+    for (Resource resource : Resource.values()) {
+      final List<String[]> resourceName = 
Arrays.asList(generateResourceName(resource),
+          generateResourceName(resource), generateResourceName(resource));
+      lock.acquireWriteLocks(resource, resourceName.subList(1, 
resourceName.size()));
+
+      AtomicBoolean gotLock = new AtomicBoolean(false);
+      new Thread(() -> {
+        lock.acquireWriteLocks(resource, resourceName.subList(0, 2));
+        gotLock.set(true);
+        lock.releaseWriteLocks(resource, resourceName.subList(0, 2));
+      }).start();
+      // Let's give some time for the new thread to run
+      Thread.sleep(100);
+      // Since the new thread is trying to get lock on same resource,
+      // it will wait.
+      assertFalse(gotLock.get());
+      lock.releaseWriteLocks(resource, resourceName.subList(1, 
resourceName.size()));
+      // Since we have released the lock, the new thread should have the lock
+      // now.
+      // Let's give some time for the new thread to run
+      Thread.sleep(100);
+      assertTrue(gotLock.get());
+    }
+
+  }
+
+
   @Test
   void testMultiLockResourceParallel() throws Exception {
     OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/MultiSnapshotLocks.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/MultiSnapshotLocks.java
new file mode 100644
index 0000000000..aac3b097b2
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/MultiSnapshotLocks.java
@@ -0,0 +1,85 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.lock.IOzoneManagerLock;
+import org.apache.hadoop.ozone.om.lock.OMLockDetails;
+import org.apache.hadoop.ozone.om.lock.OzoneManagerLock;
+
+/**
+ * Class to take multiple locks on multiple snapshots.
+ */
+public class MultiSnapshotLocks {
+  private final List<String[]> objectLocks;
+  private final IOzoneManagerLock lock;
+  private final OzoneManagerLock.Resource resource;
+  private final boolean writeLock;
+  private OMLockDetails lockDetails;
+
+  public MultiSnapshotLocks(IOzoneManagerLock lock, OzoneManagerLock.Resource 
resource, boolean writeLock) {
+    this.writeLock = writeLock;
+    this.resource = resource;
+    this.lock = lock;
+    this.objectLocks = new ArrayList<>();
+    this.lockDetails = OMLockDetails.EMPTY_DETAILS_LOCK_NOT_ACQUIRED;
+  }
+
+  public synchronized OMLockDetails acquireLock(Collection<UUID> ids) throws 
OMException {
+    if (this.lockDetails.isLockAcquired()) {
+      throw new OMException(
+          
objectLocks.stream().map(Arrays::toString).collect(Collectors.joining(",",
+              "More locks cannot be acquired when locks have been already 
acquired. Locks acquired : [", "]")),
+          OMException.ResultCodes.INTERNAL_ERROR);
+    }
+    List<String[]> keys =
+        ids.stream().filter(Objects::nonNull).map(id -> new String[] 
{id.toString()})
+            .collect(Collectors.toList());
+    OMLockDetails omLockDetails = this.writeLock ? 
lock.acquireWriteLocks(resource, keys) :
+        lock.acquireReadLocks(resource, keys);
+    if (omLockDetails.isLockAcquired()) {
+      objectLocks.addAll(keys);
+    }
+    this.lockDetails = omLockDetails;
+    return omLockDetails;
+  }
+
+  public synchronized void releaseLock() {
+    if (this.writeLock) {
+      lockDetails = lock.releaseWriteLocks(resource, this.objectLocks);
+    } else {
+      lockDetails = lock.releaseReadLocks(resource, this.objectLocks);
+    }
+    this.objectLocks.clear();
+  }
+
+  List<String[]> getObjectLocks() {
+    return objectLocks;
+  }
+
+  public boolean isLockAcquired() {
+    return lockDetails.isLockAcquired();
+  }
+}
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestMultiSnapshotLocks.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestMultiSnapshotLocks.java
new file mode 100644
index 0000000000..741f1d30c3
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestMultiSnapshotLocks.java
@@ -0,0 +1,136 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyCollection;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.lock.IOzoneManagerLock;
+import org.apache.hadoop.ozone.om.lock.OMLockDetails;
+import org.apache.hadoop.ozone.om.lock.OzoneManagerLock;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * Class to test class MultiLocks.
+ */
+@ExtendWith(MockitoExtension.class)
+public class TestMultiSnapshotLocks {
+  @Mock
+  private IOzoneManagerLock mockLock;
+
+  @Mock
+  private OzoneManagerLock.Resource mockResource;
+
+  private MultiSnapshotLocks multiSnapshotLocks;
+  private UUID obj1 = UUID.randomUUID();
+  private UUID obj2 = UUID.randomUUID();
+
+  @BeforeEach
+  void setUp() {
+    // Initialize MultiLocks with mock dependencies
+    multiSnapshotLocks = new MultiSnapshotLocks(mockLock, mockResource, true);
+  }
+
+  @Test
+  void testAcquireLockSuccess() throws Exception {
+    List<UUID> objects = Arrays.asList(obj1, obj2);
+    OMLockDetails mockLockDetails = mock(OMLockDetails.class);
+    when(mockLockDetails.isLockAcquired()).thenReturn(true);
+
+    // Simulate successful lock acquisition for each object
+    when(mockLock.acquireWriteLocks(eq(mockResource), 
anyList())).thenReturn(mockLockDetails);
+
+    OMLockDetails result = multiSnapshotLocks.acquireLock(objects);
+
+    assertEquals(mockLockDetails, result);
+    verify(mockLock, 
times(1)).acquireWriteLocks(ArgumentMatchers.eq(mockResource), any());
+  }
+
+  @Test
+  void testAcquireLockFailureReleasesAll() throws Exception {
+
+    List<UUID> objects = Arrays.asList(obj1, obj2);
+    OMLockDetails failedLockDetails = mock(OMLockDetails.class);
+    when(failedLockDetails.isLockAcquired()).thenReturn(false);
+
+    // Simulate failure during lock acquisition
+    when(mockLock.acquireWriteLocks(eq(mockResource), 
anyCollection())).thenReturn(failedLockDetails);
+
+    OMLockDetails result = multiSnapshotLocks.acquireLock(objects);
+
+    assertEquals(failedLockDetails, result);
+    assertTrue(multiSnapshotLocks.getObjectLocks().isEmpty());
+  }
+
+  @Test
+  void testReleaseLock() throws Exception {
+    List<UUID> objects = Arrays.asList(obj1, obj2);
+    OMLockDetails mockLockDetails = mock(OMLockDetails.class);
+    when(mockLockDetails.isLockAcquired()).thenReturn(true);
+
+    // Acquire locks first
+    when(mockLock.acquireWriteLocks(eq(mockResource), 
anyCollection())).thenReturn(mockLockDetails);
+    multiSnapshotLocks.acquireLock(objects);
+    assertFalse(multiSnapshotLocks.getObjectLocks().isEmpty());
+
+    // Now release locks
+    multiSnapshotLocks.releaseLock();
+
+    // Verify that locks are released in order
+    verify(mockLock).releaseWriteLocks(eq(mockResource), any());
+    assertTrue(multiSnapshotLocks.getObjectLocks().isEmpty());
+  }
+
+  @Test
+  void testAcquireLockWhenAlreadyAcquiredThrowsException() throws Exception {
+    List<UUID> objects = Collections.singletonList(obj1);
+    OMLockDetails mockLockDetails = mock(OMLockDetails.class);
+    when(mockLockDetails.isLockAcquired()).thenReturn(true);
+
+    // Acquire a lock first
+    when(mockLock.acquireWriteLocks(any(), 
anyList())).thenReturn(mockLockDetails);
+    multiSnapshotLocks.acquireLock(objects);
+
+    // Try acquiring locks again without releasing
+    OMException exception = assertThrows(OMException.class, () -> 
multiSnapshotLocks.acquireLock(objects));
+
+    assertEquals(
+        String.format("More locks cannot be acquired when locks have been 
already acquired. Locks acquired : [[%s]]",
+            obj1.toString()), exception.getMessage());
+  }
+}


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

Reply via email to