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

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


The following commit(s) were added to refs/heads/4.19 by this push:
     new 2df68021761 Allocate new ROOT volume (on restore virtual machine 
operation) only when resource count increment succeeds (#8555)
2df68021761 is described below

commit 2df68021761adbd93eecda20114894c8f2edb8bd
Author: Suresh Kumar Anaparti <[email protected]>
AuthorDate: Mon Feb 5 13:28:28 2024 +0530

    Allocate new ROOT volume (on restore virtual machine operation) only when 
resource count increment succeeds (#8555)
    
    * Allocate new volume on restore virtual machine operation when resource 
count increment succeeds
    - keep them in transaction, and fail operation if resource count increment 
fails
    
    * Added some (negative) unit tests for restore vm
---
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  | 100 +++++++-----
 .../java/com/cloud/vm/UserVmManagerImplTest.java   | 174 +++++++++++++++++++++
 2 files changed, 234 insertions(+), 40 deletions(-)

diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java 
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 2927db638ab..0d3f047809a 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -354,6 +354,7 @@ import com.cloud.utils.db.GlobalLock;
 import com.cloud.utils.db.SearchCriteria;
 import com.cloud.utils.db.Transaction;
 import com.cloud.utils.db.TransactionCallbackNoReturn;
+import com.cloud.utils.db.TransactionCallbackWithException;
 import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn;
 import com.cloud.utils.db.TransactionStatus;
 import com.cloud.utils.db.UUIDManager;
@@ -7765,49 +7766,68 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
 
         List<Volume> newVols = new ArrayList<>();
         for (VolumeVO root : rootVols) {
-            if ( !Volume.State.Allocated.equals(root.getState()) || 
newTemplateId != null ){
-                Long templateId = root.getTemplateId();
-                boolean isISO = false;
-                if (templateId == null) {
-                    // Assuming that for a vm deployed using ISO, template ID 
is set to NULL
-                    isISO = true;
-                    templateId = vm.getIsoId();
-                }
-
-                /* If new template/ISO is provided allocate a new volume from 
new template/ISO otherwise allocate new volume from original template/ISO */
-                Volume newVol = null;
-                if (newTemplateId != null) {
-                    if (isISO) {
-                        newVol = volumeMgr.allocateDuplicateVolume(root, null);
-                        vm.setIsoId(newTemplateId);
-                        vm.setGuestOSId(template.getGuestOSId());
-                        vm.setTemplateId(newTemplateId);
-                    } else {
-                        newVol = volumeMgr.allocateDuplicateVolume(root, 
newTemplateId);
-                        vm.setGuestOSId(template.getGuestOSId());
-                        vm.setTemplateId(newTemplateId);
-                    }
-                    // check and update VM if it can be dynamically scalable 
with the new template
-                    updateVMDynamicallyScalabilityUsingTemplate(vm, 
newTemplateId);
-                } else {
-                    newVol = volumeMgr.allocateDuplicateVolume(root, null);
-                }
-                newVols.add(newVol);
+            if ( !Volume.State.Allocated.equals(root.getState()) || 
newTemplateId != null ) {
+                final UserVmVO userVm = vm;
+                Pair<UserVmVO, Volume> vmAndNewVol = Transaction.execute(new 
TransactionCallbackWithException<Pair<UserVmVO, Volume>, 
CloudRuntimeException>() {
+                    @Override
+                    public Pair<UserVmVO, Volume> doInTransaction(final 
TransactionStatus status) throws CloudRuntimeException {
+                        Long templateId = root.getTemplateId();
+                        boolean isISO = false;
+                        if (templateId == null) {
+                            // Assuming that for a vm deployed using ISO, 
template ID is set to NULL
+                            isISO = true;
+                            templateId = userVm.getIsoId();
+                        }
+
+                        /* If new template/ISO is provided allocate a new 
volume from new template/ISO otherwise allocate new volume from original 
template/ISO */
+                        Volume newVol = null;
+                        if (newTemplateId != null) {
+                            if (isISO) {
+                                newVol = 
volumeMgr.allocateDuplicateVolume(root, null);
+                                userVm.setIsoId(newTemplateId);
+                                userVm.setGuestOSId(template.getGuestOSId());
+                                userVm.setTemplateId(newTemplateId);
+                            } else {
+                                newVol = 
volumeMgr.allocateDuplicateVolume(root, newTemplateId);
+                                userVm.setGuestOSId(template.getGuestOSId());
+                                userVm.setTemplateId(newTemplateId);
+                            }
+                            // check and update VM if it can be dynamically 
scalable with the new template
+                            
updateVMDynamicallyScalabilityUsingTemplate(userVm, newTemplateId);
+                        } else {
+                            newVol = volumeMgr.allocateDuplicateVolume(root, 
null);
+                        }
+                        newVols.add(newVol);
+
+                        if (userVmDetailsDao.findDetail(userVm.getId(), 
VmDetailConstants.ROOT_DISK_SIZE) == null && 
!newVol.getSize().equals(template.getSize())) {
+                            VolumeVO resizedVolume = (VolumeVO) newVol;
+                            if (template.getSize() != null) {
+                                resizedVolume.setSize(template.getSize());
+                                _volsDao.update(resizedVolume.getId(), 
resizedVolume);
+                            }
+                        }
+
+                        // 1. Save usage event and update resource count for 
user vm volumes
+                        try {
+                            
_resourceLimitMgr.incrementResourceCount(newVol.getAccountId(), 
ResourceType.volume, newVol.isDisplay());
+                            
_resourceLimitMgr.incrementResourceCount(newVol.getAccountId(),  
ResourceType.primary_storage, newVol.isDisplay(), new Long(newVol.getSize()));
+                        } catch (final CloudRuntimeException e) {
+                            throw e;
+                        } catch (final Exception e) {
+                            s_logger.error("Unable to restore VM " + 
userVm.getUuid(), e);
+                            throw new CloudRuntimeException(e);
+                        }
+
+                        // 2. Create Usage event for the newly created volume
+                        UsageEventVO usageEvent = new 
UsageEventVO(EventTypes.EVENT_VOLUME_CREATE, newVol.getAccountId(), 
newVol.getDataCenterId(), newVol.getId(), newVol.getName(), 
newVol.getDiskOfferingId(), template.getId(), newVol.getSize());
+                        _usageEventDao.persist(usageEvent);
 
-                if (userVmDetailsDao.findDetail(vm.getId(), 
VmDetailConstants.ROOT_DISK_SIZE) == null && 
!newVol.getSize().equals(template.getSize())) {
-                    VolumeVO resizedVolume = (VolumeVO) newVol;
-                    if (template.getSize() != null) {
-                        resizedVolume.setSize(template.getSize());
-                        _volsDao.update(resizedVolume.getId(), resizedVolume);
+                        return new Pair<>(userVm, newVol);
                     }
-                }
+                });
 
-                // 1. Save usage event and update resource count for user vm 
volumes
-                
_resourceLimitMgr.incrementResourceCount(newVol.getAccountId(), 
ResourceType.volume, newVol.isDisplay());
-                
_resourceLimitMgr.incrementResourceCount(newVol.getAccountId(), 
ResourceType.primary_storage, newVol.isDisplay(), new Long(newVol.getSize()));
-                // 2. Create Usage event for the newly created volume
-                UsageEventVO usageEvent = new 
UsageEventVO(EventTypes.EVENT_VOLUME_CREATE, newVol.getAccountId(), 
newVol.getDataCenterId(), newVol.getId(), newVol.getName(), 
newVol.getDiskOfferingId(), template.getId(), newVol.getSize());
-                _usageEventDao.persist(usageEvent);
+                vm = vmAndNewVol.first();
+                Volume newVol = vmAndNewVol.second();
 
                 handleManagedStorage(vm, root);
 
diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java 
b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
index 16607df7a9f..e2c2b8ef9e2 100644
--- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
+++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
@@ -30,6 +30,7 @@ import 
com.cloud.exception.InsufficientAddressCapacityException;
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.InsufficientServerCapacityException;
 import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.PermissionDeniedException;
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.host.Host;
@@ -47,6 +48,8 @@ import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.storage.DiskOfferingVO;
 import com.cloud.storage.GuestOSVO;
 import com.cloud.storage.ScopeType;
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.SnapshotVO;
 import com.cloud.storage.Storage;
 import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.Volume;
@@ -54,6 +57,7 @@ import com.cloud.storage.VolumeApiService;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.DiskOfferingDao;
 import com.cloud.storage.dao.GuestOSDao;
+import com.cloud.storage.dao.SnapshotDao;
 import com.cloud.storage.dao.VMTemplateDao;
 import com.cloud.storage.dao.VolumeDao;
 import com.cloud.template.VirtualMachineTemplate;
@@ -66,6 +70,7 @@ import com.cloud.user.UserData;
 import com.cloud.user.UserDataVO;
 import com.cloud.user.UserVO;
 import com.cloud.user.dao.AccountDao;
+import com.cloud.user.dao.UserDao;
 import com.cloud.user.dao.UserDataDao;
 import com.cloud.uservm.UserVm;
 import com.cloud.utils.Pair;
@@ -74,10 +79,14 @@ import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.dao.NicDao;
 import com.cloud.vm.dao.UserVmDao;
 import com.cloud.vm.dao.UserVmDetailsDao;
+import com.cloud.vm.snapshot.VMSnapshotVO;
+import com.cloud.vm.snapshot.dao.VMSnapshotDao;
+
 import org.apache.cloudstack.api.BaseCmd.HTTPMethod;
 import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
 import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd;
 import org.apache.cloudstack.api.command.user.vm.ResetVMUserDataCmd;
+import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd;
 import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd;
 import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
 import org.apache.cloudstack.context.CallContext;
@@ -191,6 +200,9 @@ public class UserVmManagerImplTest {
     @Mock
     private AccountDao accountDao;
 
+    @Mock
+    private UserDao userDao;
+
     @Mock
     ResourceLimitService resourceLimitMgr;
 
@@ -218,6 +230,12 @@ public class UserVmManagerImplTest {
     @Mock
     private VolumeDao volumeDaoMock;
 
+    @Mock
+    private SnapshotDao snapshotDaoMock;
+
+    @Mock
+    private VMSnapshotDao vmSnapshotDaoMock;
+
     @Mock
     AccountVO account;
 
@@ -1221,4 +1239,160 @@ public class UserVmManagerImplTest {
         Mockito.verify(userVmVoMock).setLastHostId(2L);
         Mockito.verify(userVmVoMock).setState(VirtualMachine.State.Running);
     }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRestoreVMNoVM() throws ResourceUnavailableException, 
InsufficientCapacityException {
+        CallContext callContextMock = Mockito.mock(CallContext.class);
+        
Mockito.lenient().doReturn(accountMock).when(callContextMock).getCallingAccount();
+
+        RestoreVMCmd cmd = Mockito.mock(RestoreVMCmd.class);
+        when(cmd.getVmId()).thenReturn(vmId);
+        when(cmd.getTemplateId()).thenReturn(2L);
+        when(userVmDao.findById(vmId)).thenReturn(null);
+
+        userVmManagerImpl.restoreVM(cmd);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testRestoreVMWithVolumeSnapshots() throws 
ResourceUnavailableException, InsufficientCapacityException {
+        CallContext callContextMock = Mockito.mock(CallContext.class);
+        
Mockito.lenient().doReturn(accountMock).when(callContextMock).getCallingAccount();
+        
Mockito.lenient().doNothing().when(accountManager).checkAccess(accountMock, 
null, true, userVmVoMock);
+
+        RestoreVMCmd cmd = Mockito.mock(RestoreVMCmd.class);
+        when(cmd.getVmId()).thenReturn(vmId);
+        when(cmd.getTemplateId()).thenReturn(2L);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+
+        List<VolumeVO> volumes = new ArrayList<>();
+        long rootVolumeId = 1l;
+        VolumeVO rootVolumeOfVm = Mockito.mock(VolumeVO.class);
+        Mockito.when(rootVolumeOfVm.getId()).thenReturn(rootVolumeId);
+        volumes.add(rootVolumeOfVm);
+        when(volumeDaoMock.findByInstanceAndType(vmId, 
Volume.Type.ROOT)).thenReturn(volumes);
+
+        List<SnapshotVO> snapshots = new ArrayList<>();
+        SnapshotVO snapshot = Mockito.mock(SnapshotVO.class);
+        snapshots.add(snapshot);
+        when(snapshotDaoMock.listByStatus(rootVolumeId, 
Snapshot.State.Creating, Snapshot.State.CreatedOnPrimary, 
Snapshot.State.BackingUp)).thenReturn(snapshots);
+
+        userVmManagerImpl.restoreVM(cmd);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRestoreVirtualMachineNoOwner() throws 
ResourceUnavailableException, InsufficientCapacityException {
+        long userId = 1l;
+        long accountId = 2l;
+        long newTemplateId = 2l;
+        when(accountMock.getId()).thenReturn(userId);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+        when(userVmVoMock.getAccountId()).thenReturn(accountId);
+        when(accountDao.findById(accountId)).thenReturn(null);
+
+        userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, 
newTemplateId);
+    }
+
+    @Test(expected = PermissionDeniedException.class)
+    public void testRestoreVirtualMachineOwnerDisabled() throws 
ResourceUnavailableException, InsufficientCapacityException {
+        long userId = 1l;
+        long accountId = 2l;
+        long newTemplateId = 2l;
+        when(accountMock.getId()).thenReturn(userId);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+        when(userVmVoMock.getAccountId()).thenReturn(accountId);
+        when(accountDao.findById(accountId)).thenReturn(callerAccount);
+        when(callerAccount.getState()).thenReturn(Account.State.DISABLED);
+
+        userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, 
newTemplateId);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testRestoreVirtualMachineNotInRightState() throws 
ResourceUnavailableException, InsufficientCapacityException {
+        long userId = 1l;
+        long accountId = 2l;
+        long newTemplateId = 2l;
+        when(accountMock.getId()).thenReturn(userId);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+        when(userVmVoMock.getAccountId()).thenReturn(accountId);
+        
when(userVmVoMock.getUuid()).thenReturn("a967643d-7633-4ab4-ac26-9c0b63f50cc1");
+        when(accountDao.findById(accountId)).thenReturn(callerAccount);
+        
when(userVmVoMock.getState()).thenReturn(VirtualMachine.State.Starting);
+
+        userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, 
newTemplateId);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRestoreVirtualMachineNoRootVolume() throws 
ResourceUnavailableException, InsufficientCapacityException {
+        long userId = 1l;
+        long accountId = 2l;
+        long currentTemplateId = 1l;
+        long newTemplateId = 2l;
+        when(accountMock.getId()).thenReturn(userId);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+        when(userVmVoMock.getAccountId()).thenReturn(accountId);
+        
when(userVmVoMock.getUuid()).thenReturn("a967643d-7633-4ab4-ac26-9c0b63f50cc1");
+        when(accountDao.findById(accountId)).thenReturn(callerAccount);
+        when(userVmVoMock.getState()).thenReturn(VirtualMachine.State.Running);
+        when(userVmVoMock.getTemplateId()).thenReturn(currentTemplateId);
+
+        VMTemplateVO currentTemplate = Mockito.mock(VMTemplateVO.class);
+        
when(templateDao.findById(currentTemplateId)).thenReturn(currentTemplate);
+        when(volumeDaoMock.findByInstanceAndType(vmId, 
Volume.Type.ROOT)).thenReturn(new ArrayList<VolumeVO>());
+
+        userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, 
newTemplateId);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRestoreVirtualMachineMoreThanOneRootVolume() throws 
ResourceUnavailableException, InsufficientCapacityException {
+        long userId = 1l;
+        long accountId = 2l;
+        long currentTemplateId = 1l;
+        long newTemplateId = 2l;
+        when(accountMock.getId()).thenReturn(userId);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+        when(userVmVoMock.getAccountId()).thenReturn(accountId);
+        
when(userVmVoMock.getUuid()).thenReturn("a967643d-7633-4ab4-ac26-9c0b63f50cc1");
+        when(accountDao.findById(accountId)).thenReturn(callerAccount);
+        when(userVmVoMock.getState()).thenReturn(VirtualMachine.State.Running);
+        when(userVmVoMock.getTemplateId()).thenReturn(currentTemplateId);
+
+        VMTemplateVO currentTemplate = Mockito.mock(VMTemplateVO.class);
+        when(currentTemplate.isDeployAsIs()).thenReturn(false);
+        
when(templateDao.findById(currentTemplateId)).thenReturn(currentTemplate);
+        List<VolumeVO> volumes = new ArrayList<>();
+        VolumeVO rootVolume1 = Mockito.mock(VolumeVO.class);
+        volumes.add(rootVolume1);
+        VolumeVO rootVolume2 = Mockito.mock(VolumeVO.class);
+        volumes.add(rootVolume2);
+        when(volumeDaoMock.findByInstanceAndType(vmId, 
Volume.Type.ROOT)).thenReturn(volumes);
+
+        userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, 
newTemplateId);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRestoreVirtualMachineWithVMSnapshots() throws 
ResourceUnavailableException, InsufficientCapacityException {
+        long userId = 1l;
+        long accountId = 2l;
+        long currentTemplateId = 1l;
+        long newTemplateId = 2l;
+        when(accountMock.getId()).thenReturn(userId);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+        when(userVmVoMock.getAccountId()).thenReturn(accountId);
+        when(accountDao.findById(accountId)).thenReturn(callerAccount);
+        when(userVmVoMock.getState()).thenReturn(VirtualMachine.State.Running);
+        when(userVmVoMock.getTemplateId()).thenReturn(currentTemplateId);
+
+        VMTemplateVO currentTemplate = Mockito.mock(VMTemplateVO.class);
+        
when(templateDao.findById(currentTemplateId)).thenReturn(currentTemplate);
+        List<VolumeVO> volumes = new ArrayList<>();
+        VolumeVO rootVolumeOfVm = Mockito.mock(VolumeVO.class);
+        volumes.add(rootVolumeOfVm);
+        when(volumeDaoMock.findByInstanceAndType(vmId, 
Volume.Type.ROOT)).thenReturn(volumes);
+        List<VMSnapshotVO> vmSnapshots = new ArrayList<>();
+        VMSnapshotVO vmSnapshot = Mockito.mock(VMSnapshotVO.class);
+        vmSnapshots.add(vmSnapshot);
+        when(vmSnapshotDaoMock.findByVm(vmId)).thenReturn(vmSnapshots);
+
+        userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, 
newTemplateId);
+    }
 }

Reply via email to