diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index 539503e9ffb..088517e705b 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -78,6 +78,7 @@ import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.managed.context.ManagedContextTimerTask; import org.apache.cloudstack.poll.BackgroundPollManager; import org.apache.cloudstack.poll.BackgroundPollTask; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; @@ -85,6 +86,8 @@ import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; import com.amazonaws.util.CollectionUtils; import com.cloud.alert.AlertManager; @@ -119,6 +122,7 @@ import com.cloud.network.dao.NetworkDao; import com.cloud.offering.DiskOffering; import com.cloud.offering.ServiceOffering; import com.cloud.projects.Project; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.serializer.GsonHelper; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; @@ -170,8 +174,6 @@ import com.cloud.vm.dao.VMInstanceDetailsDao; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; -import org.apache.commons.lang3.builder.ReflectionToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; public class BackupManagerImpl extends ManagerBase implements BackupManager { @@ -237,6 +239,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { private AlertManager alertManager; @Inject private GuestOSDao _guestOSDao; + @Inject + ReservationDao reservationDao; private AsyncJobDispatcher asyncJobDispatcher; private Timer backupTimer; @@ -792,14 +796,6 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { Long backupScheduleId = getBackupScheduleId(job); boolean isScheduledBackup = backupScheduleId != null; Account owner = accountManager.getAccount(vm.getAccountId()); - try { - resourceLimitMgr.checkResourceLimit(owner, Resource.ResourceType.backup); - } catch (ResourceAllocationException e) { - if (isScheduledBackup) { - sendExceededBackupLimitAlert(owner.getUuid(), Resource.ResourceType.backup); - } - throw e; - } Long backupSize = 0L; for (final Volume volume: volumeDao.findByInstance(vmId)) { @@ -811,42 +807,57 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { backupSize += volumeSize; } } - try { - resourceLimitMgr.checkResourceLimit(owner, Resource.ResourceType.backup_storage, backupSize); - } catch (ResourceAllocationException e) { - if (isScheduledBackup) { - sendExceededBackupLimitAlert(owner.getUuid(), Resource.ResourceType.backup_storage); - } - throw e; - } - - ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), - EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM ID:" + vm.getUuid(), - vmId, ApiCommandResourceType.VirtualMachine.toString(), - true, 0); - - Pair result = backupProvider.takeBackup(vm, cmd.getQuiesceVM()); - if (!result.first()) { - throw new CloudRuntimeException("Failed to create VM backup"); - } - Backup backup = result.second(); - if (backup != null) { - BackupVO vmBackup = backupDao.findById(result.second().getId()); - vmBackup.setBackupScheduleId(backupScheduleId); - if (cmd.getName() != null) { - vmBackup.setName(cmd.getName()); - } - vmBackup.setDescription(cmd.getDescription()); - backupDao.update(vmBackup.getId(), vmBackup); - resourceLimitMgr.incrementResourceCount(vm.getAccountId(), Resource.ResourceType.backup); - resourceLimitMgr.incrementResourceCount(vm.getAccountId(), Resource.ResourceType.backup_storage, backup.getSize()); - } + createCheckedBackup(cmd, owner, isScheduledBackup, backupSize, vm, vmId, backupProvider, backupScheduleId); if (isScheduledBackup) { deleteOldestBackupFromScheduleIfRequired(vmId, backupScheduleId); } return true; } + private void createCheckedBackup(CreateBackupCmd cmd, Account owner, boolean isScheduledBackup, Long backupSize, + VMInstanceVO vm, Long vmId, BackupProvider backupProvider, Long backupScheduleId) + throws ResourceAllocationException { + try (CheckedReservation backupReservation = new CheckedReservation(owner, Resource.ResourceType.backup, + 1L, reservationDao, resourceLimitMgr); + CheckedReservation backupStorageReservation = new CheckedReservation(owner, + Resource.ResourceType.backup_storage, backupSize, reservationDao, resourceLimitMgr)) { + + ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), + EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM ID:" + vm.getUuid(), + vmId, ApiCommandResourceType.VirtualMachine.toString(), + true, 0); + + Pair result = backupProvider.takeBackup(vm, cmd.getQuiesceVM()); + if (!result.first()) { + throw new CloudRuntimeException("Failed to create VM backup"); + } + Backup backup = result.second(); + if (backup != null) { + BackupVO vmBackup = backupDao.findById(result.second().getId()); + vmBackup.setBackupScheduleId(backupScheduleId); + if (cmd.getName() != null) { + vmBackup.setName(cmd.getName()); + } + vmBackup.setDescription(cmd.getDescription()); + backupDao.update(vmBackup.getId(), vmBackup); + resourceLimitMgr.incrementResourceCount(vm.getAccountId(), Resource.ResourceType.backup); + resourceLimitMgr.incrementResourceCount(vm.getAccountId(), Resource.ResourceType.backup_storage, backup.getSize()); + } + } catch (Exception e) { + if (e instanceof ResourceAllocationException) { + ResourceAllocationException rae = (ResourceAllocationException)e; + if (isScheduledBackup && (Resource.ResourceType.backup.equals(rae.getResourceType()) || + Resource.ResourceType.backup_storage.equals(rae.getResourceType()))) { + sendExceededBackupLimitAlert(owner.getUuid(), rae.getResourceType()); + } + throw rae; + } else if (e instanceof CloudRuntimeException) { + throw (CloudRuntimeException)e; + } + throw new CloudRuntimeException("Failed to create backup for VM with ID: " + vm.getUuid(), e); + } + } + /** * Sends an alert when the backup limit has been exceeded for a given account. * @@ -1538,19 +1549,35 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { throw new CloudRuntimeException(String.format("Backup offering with ID [%s] does not exist.", backup.getBackupOfferingId())); } final BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); - boolean result = backupProvider.deleteBackup(backup, forced); - if (result) { - resourceLimitMgr.decrementResourceCount(backup.getAccountId(), Resource.ResourceType.backup); - Long backupSize = backup.getSize() != null ? backup.getSize() : 0L; - resourceLimitMgr.decrementResourceCount(backup.getAccountId(), Resource.ResourceType.backup_storage, backupSize); - if (backupDao.remove(backup.getId())) { - checkAndGenerateUsageForLastBackupDeletedAfterOfferingRemove(vm, backup); - return true; - } else { - return false; + return deleteCheckedBackup(forced, backupProvider, backup, vm); + } + + private boolean deleteCheckedBackup(Boolean forced, BackupProvider backupProvider, BackupVO backup, VMInstanceVO vm) { + Account owner = accountManager.getAccount(backup.getAccountId()); + long backupSize = backup.getSize() != null ? backup.getSize() : 0L; + try (CheckedReservation backupReservation = new CheckedReservation(owner, Resource.ResourceType.backup, + backup.getId(), null, -1L, reservationDao, resourceLimitMgr); + CheckedReservation backupStorageReservation = new CheckedReservation(owner, + Resource.ResourceType.backup_storage, backup.getId(), null, -1 * backupSize, + reservationDao, resourceLimitMgr)) { + boolean result = backupProvider.deleteBackup(backup, forced); + if (result) { + resourceLimitMgr.decrementResourceCount(backup.getAccountId(), Resource.ResourceType.backup); + resourceLimitMgr.decrementResourceCount(backup.getAccountId(), Resource.ResourceType.backup_storage, backupSize); + if (backupDao.remove(backup.getId())) { + checkAndGenerateUsageForLastBackupDeletedAfterOfferingRemove(vm, backup); + return true; + } else { + return false; + } } + throw new CloudRuntimeException("Failed to delete the backup"); + } catch (Exception e) { + if (e instanceof CloudRuntimeException) { + throw (CloudRuntimeException) e; + } + throw new CloudRuntimeException("Failed to delete the backup due to: " + e.getMessage(), e); } - throw new CloudRuntimeException("Failed to delete the backup"); } /** diff --git a/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java b/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java index 5a18f16fd72..59adb3a0e56 100644 --- a/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java @@ -16,6 +16,27 @@ // under the License. package org.apache.cloudstack.storage.object; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd; +import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.reservation.dao.ReservationDao; +import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao; +import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO; +import org.apache.commons.lang3.ObjectUtils; +import org.jetbrains.annotations.NotNull; + import com.amazonaws.services.s3.internal.BucketNameUtils; import com.amazonaws.services.s3.model.IllegalBucketNameException; import com.cloud.agent.api.to.BucketTO; @@ -24,6 +45,7 @@ import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceAllocationException; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.resourcelimit.ResourceLimitManagerImpl; import com.cloud.storage.BucketVO; import com.cloud.storage.DataStoreRole; @@ -36,22 +58,6 @@ import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.GlobalLock; import com.cloud.utils.exception.CloudRuntimeException; -import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd; -import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.Configurable; -import org.apache.cloudstack.managed.context.ManagedContextRunnable; -import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao; -import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO; - -import javax.inject.Inject; -import javax.naming.ConfigurationException; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; public class BucketApiServiceImpl extends ManagerBase implements BucketApiService, Configurable { @@ -68,6 +74,8 @@ public class BucketApiServiceImpl extends ManagerBase implements BucketApiServic @Inject private BucketStatisticsDao _bucketStatisticsDao; + @Inject + ReservationDao reservationDao; private ScheduledExecutorService _executor = null; @@ -136,13 +144,33 @@ public class BucketApiServiceImpl extends ManagerBase implements BucketApiServic return null; } - resourceLimitManager.checkResourceLimit(owner, Resource.ResourceType.bucket); - resourceLimitManager.checkResourceLimit(owner, Resource.ResourceType.object_storage, (cmd.getQuota() * Resource.ResourceType.bytesToGiB)); + long size = ObjectUtils.defaultIfNull(cmd.getQuota(), 0) * Resource.ResourceType.bytesToGiB; + return createCheckedBucket(cmd, owner, size, ownerId); + } - BucketVO bucket = new BucketVO(ownerId, owner.getDomainId(), cmd.getObjectStoragePoolId(), cmd.getBucketName(), cmd.getQuota(), - cmd.isVersioning(), cmd.isEncryption(), cmd.isObjectLocking(), cmd.getPolicy()); - _bucketDao.persist(bucket); - return bucket; + @NotNull + private BucketVO createCheckedBucket(CreateBucketCmd cmd, Account owner, long size, long ownerId) throws ResourceAllocationException { + try (CheckedReservation bucketReservation = new CheckedReservation(owner, Resource.ResourceType.bucket, + 1L, reservationDao, resourceLimitManager); + CheckedReservation objectStorageReservation = new CheckedReservation(owner, + Resource.ResourceType.object_storage, size, reservationDao, resourceLimitManager)) { + BucketVO bucket = new BucketVO(ownerId, owner.getDomainId(), cmd.getObjectStoragePoolId(), cmd.getBucketName(), cmd.getQuota(), + cmd.isVersioning(), cmd.isEncryption(), cmd.isObjectLocking(), cmd.getPolicy()); + _bucketDao.persist(bucket); + resourceLimitManager.incrementResourceCount(bucket.getAccountId(), Resource.ResourceType.bucket); + if (size > 0) { + resourceLimitManager.incrementResourceCount(bucket.getAccountId(), Resource.ResourceType.object_storage, + (cmd.getQuota() * Resource.ResourceType.bytesToGiB)); + } + return bucket; + } catch (Exception e) { + if (e instanceof ResourceAllocationException) { + throw (ResourceAllocationException)e; + } else if (e instanceof CloudRuntimeException) { + throw (CloudRuntimeException)e; + } + throw new CloudRuntimeException(String.format("Failed to create bucket due to: %s", e.getMessage()), e); + } } @Override @@ -160,7 +188,6 @@ public class BucketApiServiceImpl extends ManagerBase implements BucketApiServic try { bucketTO = new BucketTO(objectStore.createBucket(bucket, objectLock)); bucketCreated = true; - resourceLimitManager.incrementResourceCount(bucket.getAccountId(), Resource.ResourceType.bucket); if (cmd.isVersioning()) { objectStore.setBucketVersioning(bucketTO); @@ -172,7 +199,6 @@ public class BucketApiServiceImpl extends ManagerBase implements BucketApiServic if (cmd.getQuota() != null) { objectStore.setQuota(bucketTO, cmd.getQuota()); - resourceLimitManager.incrementResourceCount(bucket.getAccountId(), Resource.ResourceType.object_storage, (cmd.getQuota() * Resource.ResourceType.bytesToGiB)); if (objectStoreVO.getTotalSize() != null && objectStoreVO.getTotalSize() != 0 && objectStoreVO.getAllocatedSize() != null) { Long allocatedSize = objectStoreVO.getAllocatedSize() / Resource.ResourceType.bytesToGiB; Long totalSize = objectStoreVO.getTotalSize() / Resource.ResourceType.bytesToGiB; @@ -193,11 +219,16 @@ public class BucketApiServiceImpl extends ManagerBase implements BucketApiServic _objectStoreDao.updateAllocatedSize(objectStoreVO, cmd.getQuota() * Resource.ResourceType.bytesToGiB); } } catch (Exception e) { - logger.debug("Failed to create bucket with name: "+bucket.getName(), e); + logger.debug("Failed to create bucket with name: {}", bucket.getName(), e); if(bucketCreated) { objectStore.deleteBucket(bucketTO); } _bucketDao.remove(bucket.getId()); + resourceLimitManager.decrementResourceCount(bucket.getAccountId(), Resource.ResourceType.bucket); + if (bucket.getQuota() != null) { + resourceLimitManager.decrementResourceCount(bucket.getAccountId(), Resource.ResourceType.object_storage, + (bucket.getQuota() * Resource.ResourceType.bytesToGiB)); + } throw new CloudRuntimeException("Failed to create bucket with name: "+bucket.getName()+". "+e.getMessage()); } return bucket; @@ -207,22 +238,39 @@ public class BucketApiServiceImpl extends ManagerBase implements BucketApiServic @ActionEvent(eventType = EventTypes.EVENT_BUCKET_DELETE, eventDescription = "deleting bucket") public boolean deleteBucket(long bucketId, Account caller) { Bucket bucket = _bucketDao.findById(bucketId); - BucketTO bucketTO = new BucketTO(bucket); if (bucket == null) { throw new InvalidParameterValueException("Unable to find bucket with ID: " + bucketId); } _accountMgr.checkAccess(caller, null, true, bucket); ObjectStoreVO objectStoreVO = _objectStoreDao.findById(bucket.getObjectStoreId()); ObjectStoreEntity objectStore = (ObjectStoreEntity)_dataStoreMgr.getDataStore(objectStoreVO.getId(), DataStoreRole.Object); - if (objectStore.deleteBucket(bucketTO)) { - resourceLimitManager.decrementResourceCount(bucket.getAccountId(), Resource.ResourceType.bucket); - if (bucket.getQuota() != null) { - resourceLimitManager.decrementResourceCount(bucket.getAccountId(), Resource.ResourceType.object_storage, (bucket.getQuota() * Resource.ResourceType.bytesToGiB)); - _objectStoreDao.updateAllocatedSize(objectStoreVO, -(bucket.getQuota() * Resource.ResourceType.bytesToGiB)); + return deleteCheckedBucket(objectStore, bucket, objectStoreVO); + } + + private boolean deleteCheckedBucket(ObjectStoreEntity objectStore, Bucket bucket, ObjectStoreVO objectStoreVO) { + Account owner = _accountMgr.getAccount(bucket.getAccountId()); + try (CheckedReservation bucketReservation = new CheckedReservation(owner, Resource.ResourceType.bucket, + bucket.getId(), null, -1L, reservationDao, resourceLimitManager); + CheckedReservation objectStorageReservation = new CheckedReservation(owner, + Resource.ResourceType.object_storage, bucket.getId(), null, + -1*(ObjectUtils.defaultIfNull(bucket.getQuota(), 0) * Resource.ResourceType.bytesToGiB), reservationDao, resourceLimitManager)) { + BucketTO bucketTO = new BucketTO(bucket); + if (objectStore.deleteBucket(bucketTO)) { + resourceLimitManager.decrementResourceCount(bucket.getAccountId(), Resource.ResourceType.bucket); + if (bucket.getQuota() != null) { + resourceLimitManager.decrementResourceCount(bucket.getAccountId(), Resource.ResourceType.object_storage, (bucket.getQuota() * Resource.ResourceType.bytesToGiB)); + _objectStoreDao.updateAllocatedSize(objectStoreVO, -(bucket.getQuota() * Resource.ResourceType.bytesToGiB)); + } + _bucketDao.remove(bucket.getId()); + return true; } - return _bucketDao.remove(bucketId); + return false; + } catch (Exception e) { + if (e instanceof CloudRuntimeException) { + throw (CloudRuntimeException) e; + } + throw new CloudRuntimeException(String.format("Failed to delete bucket due to: %s", e.getMessage()), e); } - return false; } @Override diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 8b13fd47494..7395f222a86 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -16,9 +16,72 @@ // under the License. package org.apache.cloudstack.backup; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.TimeZone; +import java.util.UUID; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; +import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; +import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; +import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; +import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.dao.BackupDetailsDao; +import org.apache.cloudstack.backup.dao.BackupOfferingDao; +import org.apache.cloudstack.backup.dao.BackupScheduleDao; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.reservation.ReservationVO; +import org.apache.cloudstack.reservation.dao.ReservationDao; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.commons.lang3.StringUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.springframework.test.util.ReflectionTestUtils; + +import com.cloud.alert.AlertManager; import com.cloud.api.query.dao.UserVmJoinDao; import com.cloud.api.query.vo.UserVmJoinVO; -import com.cloud.alert.AlertManager; import com.cloud.capacity.CapacityVO; import com.cloud.configuration.Resource; import com.cloud.dc.DataCenter; @@ -31,6 +94,7 @@ import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceAllocationException; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; @@ -42,7 +106,6 @@ import com.cloud.offering.DiskOffering; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; -import com.cloud.exception.ResourceAllocationException; import com.cloud.storage.Storage; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; @@ -61,6 +124,7 @@ import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; +import com.cloud.utils.db.DbUtil; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; @@ -68,67 +132,11 @@ import com.cloud.utils.fsm.NoTransitionException; import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VmDiskInfo; import com.cloud.vm.VirtualMachineManager; -import com.cloud.vm.dao.VMInstanceDetailsDao; +import com.cloud.vm.VmDiskInfo; import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; import com.google.gson.Gson; -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; -import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; -import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; -import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; -import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; -import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; -import org.apache.cloudstack.api.response.BackupResponse; -import org.apache.cloudstack.backup.dao.BackupDao; -import org.apache.cloudstack.backup.dao.BackupDetailsDao; -import org.apache.cloudstack.backup.dao.BackupOfferingDao; -import org.apache.cloudstack.backup.dao.BackupScheduleDao; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; -import org.springframework.test.util.ReflectionTestUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.TimeZone; -import java.util.UUID; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.atLeastOnce; @RunWith(MockitoJUnitRunner.class) public class BackupManagerTest { @@ -238,6 +246,9 @@ public class BackupManagerTest { @Mock DomainDao domainDao; + @Mock + ReservationDao reservationDao; + @Mock private GuestOSDao _guestOSDao; @@ -248,6 +259,8 @@ public class BackupManagerTest { private AutoCloseable closeable; private ConfigDepotImpl configDepotImpl; private boolean updatedConfigKeyDepot = false; + private MockedStatic dbUtilMockedStatic; + private final List mockedGlobalLocks = new ArrayList<>(); @Before public void setup() throws Exception { @@ -279,6 +292,29 @@ public class BackupManagerTest { backupProvidersMap.put(backupProvider.getName().toLowerCase(), backupProvider); ReflectionTestUtils.setField(backupManager, "backupProvidersMap", backupProvidersMap); + when(reservationDao.persist(any(ReservationVO.class))) + .thenAnswer((Answer) invocation -> { + ReservationVO reservationVO = (ReservationVO)invocation.getArguments()[0]; + ReflectionTestUtils.setField(reservationVO, "id", 10L); + return reservationVO; + }); + dbUtilMockedStatic = Mockito.mockStatic(DbUtil.class); + dbUtilMockedStatic.when(() -> DbUtil.getGlobalLock(anyString(), anyInt())).thenAnswer((Answer) invocation -> { + String lockName = invocation.getArgument(0); + if (!StringUtils.isBlank(lockName) && !mockedGlobalLocks.contains(lockName)) { + mockedGlobalLocks.add(lockName); + return true; + } + return false; + }); + dbUtilMockedStatic.when(() -> DbUtil.releaseGlobalLock(anyString())).thenAnswer((Answer) invocation -> { + String lockName = invocation.getArgument(0); + if (!StringUtils.isBlank(lockName)) { + mockedGlobalLocks.remove(lockName); + } + return true; + }); + Account account = mock(Account.class); User user = mock(User.class); CallContext.register(user, account); @@ -286,6 +322,7 @@ public class BackupManagerTest { @After public void tearDown() throws Exception { + dbUtilMockedStatic.close(); closeable.close(); if (updatedConfigKeyDepot) { ReflectionTestUtils.setField(BackupManager.BackupFrameworkEnabled, "s_depot", configDepotImpl); @@ -622,6 +659,7 @@ public class BackupManagerTest { Long oldestBackupId = 7L; Long newBackupSize = 1000000000L; Long oldBackupSize = 400000000L; + long domainId = 101L; when(vmInstanceDao.findById(vmId)).thenReturn(vmInstanceVOMock); when(vmInstanceVOMock.getDataCenterId()).thenReturn(zoneId); @@ -636,6 +674,7 @@ public class BackupManagerTest { Mockito.doReturn(scheduleId).when(backupManager).getBackupScheduleId(asyncJobVOMock); when(accountManager.getAccount(accountId)).thenReturn(accountVOMock); + when(accountVOMock.getDomainId()).thenReturn(domainId); BackupScheduleVO schedule = mock(BackupScheduleVO.class); when(backupScheduleDao.findById(scheduleId)).thenReturn(schedule); @@ -677,8 +716,10 @@ public class BackupManagerTest { assertTrue(backupManager.createBackup(cmd, asyncJobVOMock)); - Mockito.verify(resourceLimitMgr, times(1)).checkResourceLimit(accountVOMock, Resource.ResourceType.backup); - Mockito.verify(resourceLimitMgr, times(1)).checkResourceLimit(accountVOMock, Resource.ResourceType.backup_storage, newBackupSize); + Mockito.verify(resourceLimitMgr, times(1)) + .checkResourceLimitWithTag(accountVOMock, domainId, true, Resource.ResourceType.backup, null, 1L); + Mockito.verify(resourceLimitMgr, times(1)) + .checkResourceLimitWithTag(accountVOMock, domainId, true, Resource.ResourceType.backup_storage, null, newBackupSize); Mockito.verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup); Mockito.verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, newBackupSize); @@ -694,6 +735,7 @@ public class BackupManagerTest { Long scheduleId = 3L; Long backupOfferingId = 4L; Long accountId = 5L; + long domainId = 101L; when(vmInstanceDao.findById(vmId)).thenReturn(vmInstanceVOMock); when(vmInstanceVOMock.getDataCenterId()).thenReturn(zoneId); @@ -708,7 +750,9 @@ public class BackupManagerTest { Account account = Mockito.mock(Account.class); when(accountManager.getAccount(accountId)).thenReturn(account); - Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup)).when(resourceLimitMgr).checkResourceLimit(account, Resource.ResourceType.backup); + when(account.getDomainId()).thenReturn(domainId); + Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup)).when(resourceLimitMgr) + .checkResourceLimitWithTag(account, domainId, true, Resource.ResourceType.backup, null, 1L); CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class); when(cmd.getVmId()).thenReturn(vmId); @@ -732,6 +776,8 @@ public class BackupManagerTest { Long scheduleId = 3L; Long backupOfferingId = 4L; Long accountId = 5L; + long domainId = 101L; + long size = 10000L; VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); when(vmInstanceDao.findById(vmId)).thenReturn(vm); @@ -739,6 +785,11 @@ public class BackupManagerTest { when(vm.getBackupOfferingId()).thenReturn(backupOfferingId); when(vm.getAccountId()).thenReturn(accountId); + VolumeVO volume = mock(VolumeVO.class); + when(volumeDao.findByInstance(vmId)).thenReturn(List.of(volume)); + when(volume.getState()).thenReturn(Volume.State.Ready); + when(volumeApiService.getVolumePhysicalSize(null, null, null)).thenReturn(size); + overrideBackupFrameworkConfigValue(); BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class); when(backupOfferingDao.findById(backupOfferingId)).thenReturn(offering); @@ -749,7 +800,10 @@ public class BackupManagerTest { Account account = Mockito.mock(Account.class); when(accountManager.getAccount(accountId)).thenReturn(account); - Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup_storage)).when(resourceLimitMgr).checkResourceLimit(account, Resource.ResourceType.backup_storage, 0L); + when(account.getDomainId()).thenReturn(domainId); + Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup_storage)) + .when(resourceLimitMgr) + .checkResourceLimitWithTag(account, domainId, true, Resource.ResourceType.backup_storage, null, size); CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class); when(cmd.getVmId()).thenReturn(vmId); @@ -1487,6 +1541,7 @@ public class BackupManagerTest { when(backup.getVmId()).thenReturn(vmId); when(backup.getZoneId()).thenReturn(zoneId); when(backup.getAccountId()).thenReturn(accountId); + when(accountManager.getAccount(accountId)).thenReturn(accountVOMock); when(backup.getBackupOfferingId()).thenReturn(backupOfferingId); when(backup.getSize()).thenReturn(100L); diff --git a/server/src/test/java/org/apache/cloudstack/storage/object/BucketApiServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/storage/object/BucketApiServiceImplTest.java index b630ddc69a7..6dbdd45ce4c 100644 --- a/server/src/test/java/org/apache/cloudstack/storage/object/BucketApiServiceImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/storage/object/BucketApiServiceImplTest.java @@ -16,19 +16,36 @@ // under the License. package org.apache.cloudstack.storage.object; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd; import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.reservation.ReservationVO; +import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao; import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO; +import org.apache.commons.lang3.StringUtils; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; import org.springframework.test.util.ReflectionTestUtils; import com.cloud.agent.api.to.BucketTO; @@ -40,6 +57,9 @@ import com.cloud.storage.DataStoreRole; import com.cloud.storage.dao.BucketDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.utils.db.DbUtil; @RunWith(MockitoJUnitRunner.class) public class BucketApiServiceImplTest { @@ -62,33 +82,90 @@ public class BucketApiServiceImplTest { @Mock private BucketDao bucketDao; + @Mock + ReservationDao reservationDao; + + @Mock + private AccountVO mockAccountVO; + + private MockedStatic dbUtilMockedStatic; + private final List mockedGlobalLocks = new ArrayList<>(); + private static final long ACCOUNT_ID = 1001L; + private static final long DOMAIN_ID = 10L; + + @Before + public void setup() { + when(accountManager.getActiveAccountById(ACCOUNT_ID)).thenReturn(mockAccountVO); + when(mockAccountVO.getDomainId()).thenReturn(DOMAIN_ID); + when(reservationDao.persist(any(ReservationVO.class))) + .thenAnswer((Answer) invocation -> { + ReservationVO reservationVO = (ReservationVO)invocation.getArguments()[0]; + ReflectionTestUtils.setField(reservationVO, "id", 10L); + return reservationVO; + }); + dbUtilMockedStatic = Mockito.mockStatic(DbUtil.class); + dbUtilMockedStatic.when(() -> DbUtil.getGlobalLock(anyString(), anyInt())) + .thenAnswer((Answer) invocation -> { + String lockName = invocation.getArgument(0); + if (!StringUtils.isBlank(lockName) && !mockedGlobalLocks.contains(lockName)) { + mockedGlobalLocks.add(lockName); + return true; + } + return false; + }); + dbUtilMockedStatic.when(() -> DbUtil.releaseGlobalLock(anyString())) + .thenAnswer((Answer) invocation -> { + String lockName = invocation.getArgument(0); + if (!StringUtils.isBlank(lockName)) { + mockedGlobalLocks.remove(lockName); + } + return true; + }); + + Account account = mock(Account.class); + User user = mock(User.class); + CallContext.register(user, account); + } + + @After + public void tearDown() throws Exception { + dbUtilMockedStatic.close(); + CallContext.unregister(); + } + @Test public void testAllocBucket() throws ResourceAllocationException { String bucketName = "bucket1"; - Long accountId = 1L; Long poolId = 2L; Long objectStoreId = 3L; + int quota = 1; CreateBucketCmd cmd = Mockito.mock(CreateBucketCmd.class); Mockito.when(cmd.getBucketName()).thenReturn(bucketName); - Mockito.when(cmd.getEntityOwnerId()).thenReturn(accountId); + Mockito.when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); Mockito.when(cmd.getObjectStoragePoolId()).thenReturn(poolId); - Mockito.when(cmd.getQuota()).thenReturn(1); - - Account account = Mockito.mock(Account.class); - Mockito.when(accountManager.getActiveAccountById(accountId)).thenReturn(account); + Mockito.when(cmd.getQuota()).thenReturn(quota); ObjectStoreVO objectStoreVO = Mockito.mock(ObjectStoreVO.class); Mockito.when(objectStoreVO.getId()).thenReturn(objectStoreId); Mockito.when(objectStoreDao.findById(poolId)).thenReturn(objectStoreVO); ObjectStoreEntity objectStore = Mockito.mock(ObjectStoreEntity.class); Mockito.when(dataStoreMgr.getDataStore(objectStoreId, DataStoreRole.Object)).thenReturn(objectStore); - Mockito.when(objectStore.createUser(accountId)).thenReturn(true); + Mockito.when(objectStore.createUser(ACCOUNT_ID)).thenReturn(true); bucketApiService.allocBucket(cmd); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimit(account, Resource.ResourceType.bucket); - Mockito.verify(resourceLimitManager, Mockito.times(1)).checkResourceLimit(account, Resource.ResourceType.object_storage, 1 * Resource.ResourceType.bytesToGiB); + long size = quota * Resource.ResourceType.bytesToGiB; + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .checkResourceLimitWithTag(mockAccountVO, DOMAIN_ID, true, + Resource.ResourceType.bucket, null, 1L); + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .checkResourceLimitWithTag(mockAccountVO, DOMAIN_ID, true, + Resource.ResourceType.object_storage, null, size); + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .incrementResourceCount(ACCOUNT_ID, Resource.ResourceType.bucket); + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .incrementResourceCount(ACCOUNT_ID, Resource.ResourceType.object_storage, size); } @Test @@ -96,21 +173,21 @@ public class BucketApiServiceImplTest { Long objectStoreId = 1L; Long poolId = 2L; Long bucketId = 3L; - Long accountId = 4L; String bucketName = "bucket1"; + int quota = 3; CreateBucketCmd cmd = Mockito.mock(CreateBucketCmd.class); Mockito.when(cmd.getObjectStoragePoolId()).thenReturn(poolId); Mockito.when(cmd.getEntityId()).thenReturn(bucketId); - Mockito.when(cmd.getQuota()).thenReturn(1); + Mockito.when(cmd.getQuota()).thenReturn(quota); BucketVO bucket = new BucketVO(bucketName); Mockito.when(bucketDao.findById(bucketId)).thenReturn(bucket); - ReflectionTestUtils.setField(bucket, "accountId", accountId); + ReflectionTestUtils.setField(bucket, "accountId", ACCOUNT_ID); ObjectStoreVO objectStoreVO = Mockito.mock(ObjectStoreVO.class); Mockito.when(objectStoreVO.getId()).thenReturn(objectStoreId); - Mockito.when(objectStoreVO.getTotalSize()).thenReturn(2000000000L); + Mockito.when(objectStoreVO.getTotalSize()).thenReturn(10 * Resource.ResourceType.bytesToGiB); Mockito.when(objectStoreDao.findById(poolId)).thenReturn(objectStoreVO); ObjectStoreEntity objectStore = Mockito.mock(ObjectStoreEntity.class); Mockito.when(dataStoreMgr.getDataStore(objectStoreId, DataStoreRole.Object)).thenReturn(objectStore); @@ -118,23 +195,23 @@ public class BucketApiServiceImplTest { bucketApiService.createBucket(cmd); - Mockito.verify(resourceLimitManager, Mockito.times(1)).incrementResourceCount(accountId, Resource.ResourceType.bucket); - Mockito.verify(resourceLimitManager, Mockito.times(1)).incrementResourceCount(accountId, Resource.ResourceType.object_storage, 1 * Resource.ResourceType.bytesToGiB); - Assert.assertEquals(bucket.getState(), Bucket.State.Created); + Assert.assertEquals(Bucket.State.Created, bucket.getState()); } @Test public void testDeleteBucket() { Long bucketId = 1L; - Long accountId = 2L; Long objectStoreId = 3L; String bucketName = "bucket1"; + int quota = 2; - BucketVO bucket = new BucketVO(bucketName); + BucketVO bucket = mock(BucketVO.class); + when(bucket.getName()).thenReturn(bucketName); + when(bucket.getObjectStoreId()).thenReturn(objectStoreId); + when(bucket.getQuota()).thenReturn(quota); + when(bucket.getAccountId()).thenReturn(ACCOUNT_ID); + when(accountManager.getAccount(ACCOUNT_ID)).thenReturn(mock(AccountVO.class)); Mockito.when(bucketDao.findById(bucketId)).thenReturn(bucket); - ReflectionTestUtils.setField(bucket, "objectStoreId", objectStoreId); - ReflectionTestUtils.setField(bucket, "quota", 1); - ReflectionTestUtils.setField(bucket, "accountId", accountId); ObjectStoreVO objectStoreVO = Mockito.mock(ObjectStoreVO.class); Mockito.when(objectStoreVO.getId()).thenReturn(objectStoreId); @@ -145,15 +222,17 @@ public class BucketApiServiceImplTest { bucketApiService.deleteBucket(bucketId, null); - Mockito.verify(resourceLimitManager, Mockito.times(1)).decrementResourceCount(accountId, Resource.ResourceType.bucket); - Mockito.verify(resourceLimitManager, Mockito.times(1)).decrementResourceCount(accountId, Resource.ResourceType.object_storage, 1 * Resource.ResourceType.bytesToGiB); + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .decrementResourceCount(ACCOUNT_ID, Resource.ResourceType.bucket); + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .decrementResourceCount(ACCOUNT_ID, Resource.ResourceType.object_storage, + quota * Resource.ResourceType.bytesToGiB); } @Test public void testUpdateBucket() throws ResourceAllocationException { Long bucketId = 1L; Long objectStoreId = 2L; - Long accountId = 3L; Integer bucketQuota = 2; Integer cmdQuota = 1; String bucketName = "bucket1"; @@ -164,12 +243,10 @@ public class BucketApiServiceImplTest { BucketVO bucket = new BucketVO(bucketName); ReflectionTestUtils.setField(bucket, "quota", bucketQuota); - ReflectionTestUtils.setField(bucket, "accountId", accountId); + ReflectionTestUtils.setField(bucket, "accountId", ACCOUNT_ID); ReflectionTestUtils.setField(bucket, "objectStoreId", objectStoreId); Mockito.when(bucketDao.findById(bucketId)).thenReturn(bucket); - Account account = Mockito.mock(Account.class); - ObjectStoreVO objectStoreVO = Mockito.mock(ObjectStoreVO.class); Mockito.when(objectStoreVO.getId()).thenReturn(objectStoreId); Mockito.when(objectStoreDao.findById(objectStoreId)).thenReturn(objectStoreVO); @@ -178,6 +255,8 @@ public class BucketApiServiceImplTest { bucketApiService.updateBucket(cmd, null); - Mockito.verify(resourceLimitManager, Mockito.times(1)).decrementResourceCount(accountId, Resource.ResourceType.object_storage, (bucketQuota - cmdQuota) * Resource.ResourceType.bytesToGiB); + Mockito.verify(resourceLimitManager, Mockito.times(1)) + .decrementResourceCount(ACCOUNT_ID, Resource.ResourceType.object_storage, + (bucketQuota - cmdQuota) * Resource.ResourceType.bytesToGiB); } }