Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -6413,7 +6413,13 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE
}
_accountMgr.checkAccess(caller, null, true, snapshot);
VolumeInfo volumeOfSnapshot = getVolume(snapshot.getVolumeId(), templateId, true);
templateId = volumeOfSnapshot.getTemplateId();
if (volumeOfSnapshot != null) {
templateId = volumeOfSnapshot.getTemplateId();
} else if (templateId == null) {
throw new InvalidParameterValueException(
"Could not determine template from snapshot id=" + cmd.getSnapshotId() +
"; the source volume no longer exists. Please specify a templateId.");
}
}

VirtualMachineTemplate template = null;
Expand Down Expand Up @@ -6695,7 +6701,9 @@ protected void addLeaseDetailsForInstance(UserVm vm, Integer leaseDuration, VMLe
private VolumeInfo getVolume(long id, Long templateId, boolean isSnapshot) {
VolumeInfo volume = volFactory.getVolume(id);
if (volume != null) {
if (volume.getDataStore() == null || !ScopeType.ZONE.equals(volume.getDataStore().getScope().getScopeType())) {
if (!isSnapshot
&& (volume.getDataStore() == null
|| !ScopeType.ZONE.equals(volume.getDataStore().getScope().getScopeType()))) {
throw new InvalidParameterValueException("Deployment of virtual machine is supported only for Zone-wide storage pools");
}
checkIfVolumeTemplateIsTheSameAsTheProvided(volume, templateId);
Expand Down
134 changes: 131 additions & 3 deletions server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3908,9 +3908,6 @@ public void createVirtualMachineWithExistingSnapshot() throws ResourceUnavailabl
when(snapshotMock.getVolumeId()).thenReturn(volumeId);
when(volumeInfo.getTemplateId()).thenReturn(templateId);
when(volumeInfo.getInstanceId()).thenReturn(null);
when(volumeInfo.getDataStore()).thenReturn(primaryDataStore);
when(primaryDataStore.getScope()).thenReturn(scopeMock);
when(primaryDataStore.getScope().getScopeType()).thenReturn(ScopeType.ZONE);
when(templateMock.getTemplateType()).thenReturn(Storage.TemplateType.VNF);
when(templateMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
when(templateMock.isDeployAsIs()).thenReturn(false);
Expand All @@ -3927,6 +3924,137 @@ public void createVirtualMachineWithExistingSnapshot() throws ResourceUnavailabl
userVmManagerImpl.createVirtualMachine(deployVMCmd);
}

/**
* Bug fix: when deploying a VM from a snapshot whose source volume resided on
* local (HOST-scoped) storage, the zone-wide pool check must be skipped.
* The source volume's pool type is irrelevant – only the snapshot matters.
*/
@Test
public void createVirtualMachineWithSnapshotFromExpungedLocalStorageVolumeSucceeds()
throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
Comment thread
DaanHoogland marked this conversation as resolved.
Outdated
DeployVMCmd deployVMCmd = new DeployVMCmd();
ReflectionTestUtils.setField(deployVMCmd, "zoneId", zoneId);
Comment thread
DaanHoogland marked this conversation as resolved.
Outdated
ReflectionTestUtils.setField(deployVMCmd, "serviceOfferingId", serviceOfferingId);
ReflectionTestUtils.setField(deployVMCmd, "snapshotId", snashotId);
deployVMCmd._accountService = accountService;

when(accountService.finalyzeAccountId(nullable(String.class), nullable(Long.class), nullable(Long.class), eq(true))).thenReturn(accountId);
when(accountService.getActiveAccountById(accountId)).thenReturn(account);
when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(_dcMock);
when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOffering);
when(snapshotDaoMock.findById(snashotId)).thenReturn(snapshotMock);
when(snapshotMock.getVolumeId()).thenReturn(volumeId);
when(volumeDataFactory.getVolume(volumeId)).thenReturn(volumeInfo);
when(volumeInfo.getTemplateId()).thenReturn(templateId);
when(volumeInfo.getInstanceId()).thenReturn(null);
when(entityManager.findByIdIncludingRemoved(VirtualMachineTemplate.class, templateId)).thenReturn(templateMock);
when(templateMock.getTemplateType()).thenReturn(Storage.TemplateType.VNF);
when(templateMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
when(templateMock.isDeployAsIs()).thenReturn(false);
when(templateMock.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
when(templateMock.getUserDataId()).thenReturn(null);
Mockito.doNothing().when(vnfTemplateManager).validateVnfApplianceNics(any(), nullable(List.class), any());
when(_dcMock.isLocalStorageEnabled()).thenReturn(true);
when(_dcMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic);
Mockito.doReturn(userVmVoMock).when(userVmManagerImpl).createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(),
any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(),
any(), any(), any(), any(), eq(true), any(), any(), any());

// Must NOT throw "Deployment of virtual machine is supported only for Zone-wide storage pools"
userVmManagerImpl.createVirtualMachine(deployVMCmd);
}

/**
* Bug fix: when deploying a VM from a snapshot whose source volume's storage pool
* was expunged (data store is null), the zone-wide pool check must be skipped.
*/
@Test
public void createVirtualMachineWithSnapshotFromVolumeWithNullDataStoreSucceeds()
throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
DeployVMCmd deployVMCmd = new DeployVMCmd();
Comment thread
DaanHoogland marked this conversation as resolved.
Outdated
ReflectionTestUtils.setField(deployVMCmd, "zoneId", zoneId);
ReflectionTestUtils.setField(deployVMCmd, "serviceOfferingId", serviceOfferingId);
ReflectionTestUtils.setField(deployVMCmd, "snapshotId", snashotId);
deployVMCmd._accountService = accountService;

when(accountService.finalyzeAccountId(nullable(String.class), nullable(Long.class), nullable(Long.class), eq(true))).thenReturn(accountId);
when(accountService.getActiveAccountById(accountId)).thenReturn(account);
when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(_dcMock);
when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOffering);
when(snapshotDaoMock.findById(snashotId)).thenReturn(snapshotMock);
when(snapshotMock.getVolumeId()).thenReturn(volumeId);
when(volumeDataFactory.getVolume(volumeId)).thenReturn(volumeInfo);
when(volumeInfo.getTemplateId()).thenReturn(templateId);
when(volumeInfo.getInstanceId()).thenReturn(null);
when(entityManager.findByIdIncludingRemoved(VirtualMachineTemplate.class, templateId)).thenReturn(templateMock);
when(templateMock.getTemplateType()).thenReturn(Storage.TemplateType.VNF);
when(templateMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
when(templateMock.isDeployAsIs()).thenReturn(false);
when(templateMock.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
when(templateMock.getUserDataId()).thenReturn(null);
Mockito.doNothing().when(vnfTemplateManager).validateVnfApplianceNics(any(), nullable(List.class), any());
when(_dcMock.isLocalStorageEnabled()).thenReturn(true);
when(_dcMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic);
Mockito.doReturn(userVmVoMock).when(userVmManagerImpl).createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(),
any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(),
any(), any(), any(), any(), eq(true), any(), any(), any());

// Must NOT throw "Deployment of virtual machine is supported only for Zone-wide storage pools"
userVmManagerImpl.createVirtualMachine(deployVMCmd);
}

/**
* Regression: deploying directly from a volume (not a snapshot) whose storage pool
* is HOST-scoped (local) must still be rejected with an appropriate error.
*/
@Test
public void createVirtualMachineWithVolumeFromNonZoneScopedStorageFails() {
DeployVMCmd deployVMCmd = new DeployVMCmd();
ReflectionTestUtils.setField(deployVMCmd, "zoneId", zoneId);
ReflectionTestUtils.setField(deployVMCmd, "serviceOfferingId", serviceOfferingId);
ReflectionTestUtils.setField(deployVMCmd, "volumeId", volumeId);
deployVMCmd._accountService = accountService;

when(accountService.finalyzeAccountId(nullable(String.class), nullable(Long.class), nullable(Long.class), eq(true))).thenReturn(accountId);
when(accountService.getActiveAccountById(accountId)).thenReturn(account);
when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(_dcMock);
when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOffering);
when(volumeDataFactory.getVolume(volumeId)).thenReturn(volumeInfo);
// Volume lives on HOST-scoped (local) storage
when(volumeInfo.getDataStore()).thenReturn(primaryDataStore);
when(primaryDataStore.getScope()).thenReturn(scopeMock);
when(scopeMock.getScopeType()).thenReturn(ScopeType.HOST);

InvalidParameterValueException ex = assertThrows(InvalidParameterValueException.class,
() -> userVmManagerImpl.createVirtualMachine(deployVMCmd));
assertEquals("Deployment of virtual machine is supported only for Zone-wide storage pools", ex.getMessage());
}

/**
* Regression: deploying directly from a volume (not a snapshot) whose storage pool
* has been expunged (null data store) must still be rejected with an appropriate error.
*/
@Test
public void createVirtualMachineWithVolumeWithNullDataStoreFails() {
DeployVMCmd deployVMCmd = new DeployVMCmd();
ReflectionTestUtils.setField(deployVMCmd, "zoneId", zoneId);
ReflectionTestUtils.setField(deployVMCmd, "serviceOfferingId", serviceOfferingId);
ReflectionTestUtils.setField(deployVMCmd, "volumeId", volumeId);
deployVMCmd._accountService = accountService;

when(accountService.finalyzeAccountId(nullable(String.class), nullable(Long.class), nullable(Long.class), eq(true))).thenReturn(accountId);
when(accountService.getActiveAccountById(accountId)).thenReturn(account);
when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(_dcMock);
when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOffering);
when(volumeDataFactory.getVolume(volumeId)).thenReturn(volumeInfo);
// Volume's data store is null (pool was deleted)
when(volumeInfo.getDataStore()).thenReturn(null);

InvalidParameterValueException ex = assertThrows(InvalidParameterValueException.class,
() -> userVmManagerImpl.createVirtualMachine(deployVMCmd));
assertEquals("Deployment of virtual machine is supported only for Zone-wide storage pools", ex.getMessage());
}

@Test
public void testAllocateVMFromBackupWithVmSettingsRestoration() throws InsufficientCapacityException, ResourceAllocationException, ResourceUnavailableException {
Long backupId = 10L;
Expand Down
Loading