server: reserve backup, bucket resource limits during operations

Changes to check resource limits with reservations for the following
resource types:
- backup
- backup_storage
- bnucket
- object_storage

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
Abhishek Kumar 2026-02-26 17:46:19 +05:30 committed by dahn
parent 8608b4edd0
commit 19b4ef1069
4 changed files with 385 additions and 176 deletions

View File

@ -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<Boolean, Backup> 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<Boolean, Backup> 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");
}
/**

View File

@ -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

View File

@ -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<DbUtil> dbUtilMockedStatic;
private final List<String> 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<ReservationVO>) 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<Boolean>) 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<Boolean>) 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);

View File

@ -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<DbUtil> dbUtilMockedStatic;
private final List<String> 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<ReservationVO>) 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<Boolean>) 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<Boolean>) 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);
}
}