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

DaanHoogland pushed a commit to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 639ceeea619701831a89f0ab95b1c362549bf3bc
Author: Fabricio Duarte <[email protected]>
AuthorDate: Wed Mar 18 06:57:02 2026 +0530

    Fix snapshot copy resource limit concurrency
---
 .../storage/snapshot/SnapshotManagerImpl.java      | 36 ++++++++++++----------
 .../storage/snapshot/SnapshotManagerImplTest.java  |  4 +--
 2 files changed, 20 insertions(+), 20 deletions(-)

diff --git 
a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java 
b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java
index f9057a3434f..0d6e9de509f 100755
--- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java
+++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java
@@ -1807,15 +1807,17 @@ public class SnapshotManagerImpl extends 
MutualExclusiveIdsManagerBase implement
 
     @DB
     private boolean copySnapshotToZone(SnapshotDataStoreVO 
snapshotDataStoreVO, DataStore srcSecStore,
-           DataCenterVO dstZone, DataStore dstSecStore, Account account)
+                                       DataCenterVO dstZone, DataStore 
dstSecStore, Account account, boolean shouldCheckResourceLimits)
             throws ResourceAllocationException {
         final long snapshotId = snapshotDataStoreVO.getSnapshotId();
         final long dstZoneId = dstZone.getId();
         if (checkAndProcessSnapshotAlreadyExistInStore(snapshotId, 
dstSecStore)) {
             return true;
         }
-        try (CheckedReservation secStorageReservation = new 
CheckedReservation(account, ResourceType.secondary_storage,
-                snapshotDataStoreVO.getSize(), reservationDao, 
_resourceLimitMgr)) {
+        // Resource limit checks are not performed here at the moment, but 
they were added in case this method is used
+        // in the future to copy a standalone snapshot
+        long requiredSecondaryStorageSpace = shouldCheckResourceLimits ? 
snapshotDataStoreVO.getSize() : 0L;
+        try (CheckedReservation secondaryStorageReservation = new 
CheckedReservation(account, ResourceType.secondary_storage, null, null, null, 
requiredSecondaryStorageSpace, null, reservationDao, _resourceLimitMgr)) {
             SnapshotInfo snapshotOnSecondary = 
snapshotFactory.getSnapshot(snapshotId, srcSecStore);
             String copyUrl = null;
             try {
@@ -1846,6 +1848,7 @@ public class SnapshotManagerImpl extends 
MutualExclusiveIdsManagerBase implement
                     
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_COPY, 
account.getId(), dstZoneId, snapshotId, null, null, null, snapshotVO.getSize(),
                             snapshotVO.getSize(), 
snapshotVO.getClass().getName(), snapshotVO.getUuid());
                 }
+
                 return true;
             } catch (InterruptedException | ExecutionException | 
ResourceUnavailableException ex) {
                 logger.debug("Failed to copy snapshot ID: {} to image store: 
{}", snapshotId, dstSecStore);
@@ -1882,13 +1885,6 @@ public class SnapshotManagerImpl extends 
MutualExclusiveIdsManagerBase implement
         if (CollectionUtils.isEmpty(snapshotChain)) {
             return true;
         }
-        try {
-            _resourceLimitMgr.checkResourceLimit(account, 
ResourceType.secondary_storage, size);
-        } catch (ResourceAllocationException e) {
-            logger.error(String.format("Unable to allocate secondary storage 
resources for snapshot chain for %s with size: %d", snapshotVO, size), e);
-            return false;
-        }
-        Collections.reverse(snapshotChain);
         if (dstSecStore == null) {
             // find all eligible image stores for the destination zone
             List<DataStore> dstSecStores = 
dataStoreMgr.getImageStoresByScopeExcludingReadOnly(new ZoneScope(destZoneId));
@@ -1900,15 +1896,21 @@ public class SnapshotManagerImpl extends 
MutualExclusiveIdsManagerBase implement
                 throw new StorageUnavailableException("Destination zone is not 
ready, no image store with free capacity", DataCenter.class, destZoneId);
             }
         }
-        logger.debug("Copying snapshot chain for snapshot ID: {} on secondary 
store: {} of zone ID: {}", snapshotVO, dstSecStore, destZone);
-        for (SnapshotDataStoreVO snapshotDataStoreVO : snapshotChain) {
-            if (!copySnapshotToZone(snapshotDataStoreVO, srcSecStore, 
destZone, dstSecStore, account)) {
-                logger.error("Failed to copy snapshot: {} to zone: {} due to 
failure to copy snapshot ID: {} from snapshot chain",
-                        snapshotVO, destZone, 
snapshotDataStoreVO.getSnapshotId());
-                return false;
+        try  (CheckedReservation secondaryStorageReservation = new 
CheckedReservation(account, ResourceType.secondary_storage, null, null, null, 
size, null, reservationDao, _resourceLimitMgr)) {
+            logger.debug("Copying snapshot chain for snapshot ID: {} on 
secondary store: {} of zone ID: {}", snapshotVO, dstSecStore, destZone);
+            Collections.reverse(snapshotChain);
+            for (SnapshotDataStoreVO snapshotDataStoreVO : snapshotChain) {
+                if (!copySnapshotToZone(snapshotDataStoreVO, srcSecStore, 
destZone, dstSecStore, account, false)) {
+                    logger.error("Failed to copy snapshot: {} to zone: {} due 
to failure to copy snapshot ID: {} from snapshot chain",
+                            snapshotVO, destZone, 
snapshotDataStoreVO.getSnapshotId());
+                    return false;
+                }
             }
+            return true;
+        } catch (ResourceAllocationException e) {
+            logger.error(String.format("Unable to allocate secondary storage 
resources for snapshot chain for %s with size: %d", snapshotVO, size), e);
+            return false;
         }
-        return true;
     }
 
     @DB
diff --git 
a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java 
b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java
index 86fdcfecc13..32b103f39d3 100644
--- 
a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java
+++ 
b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java
@@ -46,7 +46,6 @@ import com.cloud.dc.dao.DataCenterDao;
 import com.cloud.event.ActionEventUtils;
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.PermissionDeniedException;
-import com.cloud.exception.ResourceAllocationException;
 import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.org.Grouping;
 import com.cloud.storage.DataStoreRole;
@@ -284,12 +283,11 @@ public class SnapshotManagerImplTest {
         Mockito.when(result1.isFailed()).thenReturn(false);
         AsyncCallFuture<SnapshotResult> future1 = 
Mockito.mock(AsyncCallFuture.class);
         try {
-            
Mockito.doNothing().when(resourceLimitService).checkResourceLimit(Mockito.any(),
 Mockito.any(), Mockito.anyLong());
             Mockito.when(future.get()).thenReturn(result);
             
Mockito.when(snapshotService.queryCopySnapshot(Mockito.any())).thenReturn(future);
             Mockito.when(future1.get()).thenReturn(result1);
             
Mockito.when(snapshotService.copySnapshot(Mockito.any(SnapshotInfo.class), 
Mockito.anyString(), Mockito.any(DataStore.class))).thenReturn(future1);
-        } catch (ResourceAllocationException | ResourceUnavailableException | 
ExecutionException | InterruptedException e) {
+        } catch (ResourceUnavailableException | ExecutionException | 
InterruptedException e) {
             Assert.fail(e.getMessage());
         }
         List<Long> addedZone = new ArrayList<>();

Reply via email to