From f25e0cb560033e289c45c250a0a813664f545db7 Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Tue, 25 Feb 2025 17:39:55 +0200 Subject: [PATCH 1/8] Option to deploy a VM with existing volume/snapshot --- .../main/java/com/cloud/vm/UserVmService.java | 8 +- .../api/command/user/vm/DeployVMCmd.java | 26 +- .../com/cloud/vm/VirtualMachineManager.java | 8 +- .../service/VolumeOrchestrationService.java | 2 +- .../service/api/OrchestrationService.java | 16 +- .../cloud/vm/VirtualMachineManagerImpl.java | 17 +- .../orchestration/CloudOrchestrator.java | 14 +- .../orchestration/VolumeOrchestrator.java | 40 ++- ...esClusterResourceModifierActionWorker.java | 4 +- .../KubernetesClusterStartWorker.java | 12 +- .../network/lb/LoadBalanceRuleHandler.java | 2 +- .../lb/InternalLoadBalancerVMManagerImpl.java | 2 +- ...InternalLoadBalancerVMManagerImplTest.java | 10 +- .../management/ServiceManagerImpl.java | 2 +- .../network/vm/NetScalerVMManagerImpl.java | 2 +- .../lifecycle/StorageVmSharedFSLifeCycle.java | 52 ++-- .../StorageVmSharedFSLifeCycleTest.java | 67 +++-- .../consoleproxy/ConsoleProxyManagerImpl.java | 2 +- .../network/as/AutoScaleManagerImpl.java | 6 +- .../network/router/NetworkHelperImpl.java | 2 +- .../network/router/VpcNetworkHelperImpl.java | 2 +- .../java/com/cloud/vm/UserVmManagerImpl.java | 150 +++++++--- .../network/as/AutoScaleManagerImplTest.java | 12 +- .../com/cloud/vm/UserVmManagerImplTest.java | 106 ++++++- .../SecondaryStorageManagerImpl.java | 2 +- ...st_vm_lifecycle_with_snapshot_or_volume.py | 278 ++++++++++++++++++ tools/marvin/marvin/lib/base.py | 16 +- 27 files changed, 695 insertions(+), 165 deletions(-) create mode 100644 test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java index 5c1c2f9a2e5c..a8ed62fb6b96 100644 --- a/api/src/main/java/com/cloud/vm/UserVmService.java +++ b/api/src/main/java/com/cloud/vm/UserVmService.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.vm; +import com.cloud.storage.Snapshot; +import com.cloud.storage.Volume; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -222,7 +224,7 @@ UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering s String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameter, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, - Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId) throws InsufficientCapacityException, + Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** @@ -298,7 +300,7 @@ UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOfferin List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, - Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, String vmType) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; + Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, String vmType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** * Creates a User VM in Advanced Zone (Security Group feature is disabled) @@ -370,7 +372,7 @@ UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffe String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, - Map templateOvfPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId) + Map templateOvfPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index bf6e41e6d59d..551570016383 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -30,10 +30,12 @@ import com.cloud.offering.DiskOffering; import com.cloud.template.VirtualMachineTemplate; import com.cloud.uservm.UserVm; +import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.Dhcp; import com.cloud.utils.net.NetUtils; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VmDetailConstants; + import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ACL; @@ -55,9 +57,11 @@ import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.SnapshotResponse; import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.UserDataResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.vm.lease.VMLeaseManager; @@ -95,7 +99,7 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG private Long serviceOfferingId; @ACL - @Parameter(name = ApiConstants.TEMPLATE_ID, type = CommandType.UUID, entityType = TemplateResponse.class, required = true, description = "the ID of the template for the virtual machine") + @Parameter(name = ApiConstants.TEMPLATE_ID, type = CommandType.UUID, entityType = TemplateResponse.class, description = "the ID of the template for the virtual machine") private Long templateId; @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "host name for the virtual machine", validations = {ApiArgValidator.RFCComplianceDomainName}) @@ -286,6 +290,11 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG description = "Lease expiry action, valid values are STOP and DESTROY") private String leaseExpiryAction; + @Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.UUID, entityType = VolumeResponse.class, since = "4.21") + private Long volumeId; + + @Parameter(name = ApiConstants.SNAPSHOT_ID, type = CommandType.UUID, entityType = SnapshotResponse.class, since = "4.21") + private Long snapshotId; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -744,6 +753,18 @@ public ApiConstants.IoDriverPolicy getIoDriverPolicy() { } return null; } + + public Long getVolumeId() { + return volumeId; + } + + public Long getSnapshotId() { + return snapshotId; + } + + public boolean isVolumeOrSnapshotProvided() { + return volumeId != null || snapshotId != null; + } ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -840,6 +861,9 @@ public void execute() { @Override public void create() throws ResourceAllocationException { + if (!isVolumeOrSnapshotProvided() && templateId == null) { + throw new CloudRuntimeException("There isn't a ROOT volume, snapshot of ROOT volume or template provided to deploy a Virtual machine"); + } try { UserVm vm = _userVmService.createVirtualMachine(this); diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index 238a78e89aff..7841eba524ac 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.vm; +import com.cloud.storage.Snapshot; +import com.cloud.storage.Volume; import java.net.URI; import java.util.HashMap; import java.util.LinkedHashMap; @@ -129,11 +131,11 @@ interface Topics { * @throws InsufficientCapacityException If there are insufficient capacity to deploy this vm. */ void allocate(String vmInstanceName, VirtualMachineTemplate template, ServiceOffering serviceOffering, DiskOfferingInfo rootDiskOfferingInfo, - List dataDiskOfferings, LinkedHashMap> auxiliaryNetworks, DeploymentPlan plan, - HypervisorType hyperType, Map> extraDhcpOptions, Map datadiskTemplateToDiskOfferingMap) throws InsufficientCapacityException; + List dataDiskOfferings, LinkedHashMap> auxiliaryNetworks, DeploymentPlan plan, + HypervisorType hyperType, Map> extraDhcpOptions, Map datadiskTemplateToDiskOfferingMap, Volume volume, Snapshot snapshot) throws InsufficientCapacityException; void allocate(String vmInstanceName, VirtualMachineTemplate template, ServiceOffering serviceOffering, - LinkedHashMap> networkProfiles, DeploymentPlan plan, HypervisorType hyperType) throws InsufficientCapacityException; + LinkedHashMap> networkProfiles, DeploymentPlan plan, HypervisorType hyperType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException; void start(String vmUuid, Map params); diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java index afc33eb51904..ccb5bba1c0a4 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java @@ -149,7 +149,7 @@ DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Lon * Allocate a volume or multiple volumes in case of template is registered with the 'deploy-as-is' option, allowing multiple disks */ List allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm, - Account owner); + Account owner, Volume volume, Snapshot snapshot); String getVmNameFromVolumeId(long volumeId); diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java index 3ffa496b5445..ffe85818fc46 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java @@ -18,6 +18,8 @@ */ package org.apache.cloudstack.engine.service.api; +import com.cloud.storage.Snapshot; +import com.cloud.storage.Volume; import java.net.URL; import java.util.List; import java.util.Map; @@ -62,12 +64,12 @@ public interface OrchestrationService { @POST @Path("/createvm") VirtualMachineEntity createVirtualMachine(@QueryParam("id") String id, @QueryParam("owner") String owner, @QueryParam("template-id") String templateId, - @QueryParam("host-name") String hostName, @QueryParam("display-name") String displayName, @QueryParam("hypervisor") String hypervisor, - @QueryParam("cpu") int cpu, @QueryParam("speed") int speed, @QueryParam("ram") long memory, @QueryParam("disk-size") Long diskSize, - @QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags, - @QueryParam("network-nic-map") Map> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, - @QueryParam("root-disk-size") Long rootDiskSize, @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, - @QueryParam("datadisktemplate-diskoffering-map") Map datadiskTemplateToDiskOfferingMap, @QueryParam("disk-offering-id") Long diskOfferingId, @QueryParam("root-disk-offering-id") Long rootDiskOfferingId) throws InsufficientCapacityException; + @QueryParam("host-name") String hostName, @QueryParam("display-name") String displayName, @QueryParam("hypervisor") String hypervisor, + @QueryParam("cpu") int cpu, @QueryParam("speed") int speed, @QueryParam("ram") long memory, @QueryParam("disk-size") Long diskSize, + @QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags, + @QueryParam("network-nic-map") Map> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, + @QueryParam("root-disk-size") Long rootDiskSize, @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, + @QueryParam("datadisktemplate-diskoffering-map") Map datadiskTemplateToDiskOfferingMap, @QueryParam("disk-offering-id") Long diskOfferingId, @QueryParam("root-disk-offering-id") Long rootDiskOfferingId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException; @POST VirtualMachineEntity createVirtualMachineFromScratch(@QueryParam("id") String id, @QueryParam("owner") String owner, @QueryParam("iso-id") String isoId, @@ -75,7 +77,7 @@ VirtualMachineEntity createVirtualMachineFromScratch(@QueryParam("id") String id @QueryParam("os") String os, @QueryParam("cpu") int cpu, @QueryParam("speed") int speed, @QueryParam("ram") long memory, @QueryParam("disk-size") Long diskSize, @QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags, @QueryParam("network-nic-map") Map> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, - @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, @QueryParam("disk-offering-id") Long diskOfferingId) throws InsufficientCapacityException; + @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, @QueryParam("disk-offering-id") Long diskOfferingId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException; @POST NetworkEntity createNetwork(String id, String name, String domainName, String cidr, String gateway); diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 13475579f771..e439872dfb55 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -49,6 +49,7 @@ import javax.naming.ConfigurationException; import javax.persistence.EntityExistsException; + import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -230,6 +231,7 @@ import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.ScopeType; +import com.cloud.storage.Snapshot; import com.cloud.storage.Storage; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.StorageManager; @@ -291,6 +293,7 @@ import com.cloud.vm.snapshot.dao.VMSnapshotDao; import com.google.gson.Gson; + public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable { public static final String VM_WORK_JOB_HANDLER = VirtualMachineManagerImpl.class.getSimpleName(); @@ -503,8 +506,8 @@ public void registerGuru(final VirtualMachine.Type type, final VirtualMachineGur @Override @DB public void allocate(final String vmInstanceName, final VirtualMachineTemplate template, final ServiceOffering serviceOffering, - final DiskOfferingInfo rootDiskOfferingInfo, final List dataDiskOfferings, - final LinkedHashMap> auxiliaryNetworks, final DeploymentPlan plan, final HypervisorType hyperType, final Map> extraDhcpOptions, final Map datadiskTemplateToDiskOfferingMap) + final DiskOfferingInfo rootDiskOfferingInfo, final List dataDiskOfferings, + final LinkedHashMap> auxiliaryNetworks, final DeploymentPlan plan, final HypervisorType hyperType, final Map> extraDhcpOptions, final Map datadiskTemplateToDiskOfferingMap, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { logger.info("allocating virtual machine from template: {} with hostname: {} and {} networks", template, vmInstanceName, auxiliaryNetworks.size()); @@ -542,7 +545,7 @@ public void allocate(final String vmInstanceName, final VirtualMachineTemplate t logger.debug("Allocating disks for {}", persistedVm); - allocateRootVolume(persistedVm, template, rootDiskOfferingInfo, owner, rootDiskSizeFinal); + allocateRootVolume(persistedVm, template, rootDiskOfferingInfo, owner, rootDiskSizeFinal, volume, snapshot); // Create new Volume context and inject event resource type, id and details to generate VOLUME.CREATE event for the ROOT disk. CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); @@ -583,7 +586,7 @@ public void allocate(final String vmInstanceName, final VirtualMachineTemplate t } } - private void allocateRootVolume(VMInstanceVO vm, VirtualMachineTemplate template, DiskOfferingInfo rootDiskOfferingInfo, Account owner, Long rootDiskSizeFinal) { + private void allocateRootVolume(VMInstanceVO vm, VirtualMachineTemplate template, DiskOfferingInfo rootDiskOfferingInfo, Account owner, Long rootDiskSizeFinal, Volume volume, Snapshot snapshot) { // Create new Volume context and inject event resource type, id and details to generate VOLUME.CREATE event for the ROOT disk. CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); try { @@ -595,7 +598,7 @@ private void allocateRootVolume(VMInstanceVO vm, VirtualMachineTemplate template logger.debug("%s has format [{}]. Skipping ROOT volume [{}] allocation.", template.toString(), ImageFormat.BAREMETAL, rootVolumeName); } else { volumeMgr.allocateTemplatedVolumes(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskSizeFinal, - rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vm, owner); + rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vm, owner, volume, snapshot); } } finally { // Remove volumeContext and pop vmContext back @@ -605,9 +608,9 @@ private void allocateRootVolume(VMInstanceVO vm, VirtualMachineTemplate template @Override public void allocate(final String vmInstanceName, final VirtualMachineTemplate template, final ServiceOffering serviceOffering, - final LinkedHashMap> networks, final DeploymentPlan plan, final HypervisorType hyperType) throws InsufficientCapacityException { + final LinkedHashMap> networks, final DeploymentPlan plan, final HypervisorType hyperType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { DiskOffering diskOffering = _diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); - allocate(vmInstanceName, template, serviceOffering, new DiskOfferingInfo(diskOffering), new ArrayList<>(), networks, plan, hyperType, null, null); + allocate(vmInstanceName, template, serviceOffering, new DiskOfferingInfo(diskOffering), new ArrayList<>(), networks, plan, hyperType, null, null, volume, snapshot); } VirtualMachineGuru getVmGuru(final VirtualMachine vm) { diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java index 6763a13aed63..f6fe76611558 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java @@ -18,6 +18,8 @@ */ package org.apache.cloudstack.engine.orchestration; +import com.cloud.storage.Snapshot; +import com.cloud.storage.Volume; import java.net.URL; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -158,8 +160,8 @@ public void destroyVolume(String volumeEntity) { @Override public VirtualMachineEntity createVirtualMachine(String id, String owner, String templateId, String hostName, String displayName, String hypervisor, int cpu, - int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map> networkNicMap, DeploymentPlan plan, - Long rootDiskSize, Map> extraDhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Long dataDiskOfferingId, Long rootDiskOfferingId) throws InsufficientCapacityException { + int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map> networkNicMap, DeploymentPlan plan, + Long rootDiskSize, Map> extraDhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Long dataDiskOfferingId, Long rootDiskOfferingId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { // VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, // vmEntityManager); @@ -255,8 +257,8 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String } } - _itMgr.allocate(vm.getInstanceName(), _templateDao.findById(new Long(templateId)), computeOffering, rootDiskOfferingInfo, dataDiskOfferings, networkIpMap, plan, - hypervisorType, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap); + _itMgr.allocate(vm.getInstanceName(), _templateDao.findByIdIncludingRemoved(new Long(templateId)), computeOffering, rootDiskOfferingInfo, dataDiskOfferings, networkIpMap, plan, + hypervisorType, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap, volume, snapshot); return vmEntity; } @@ -264,7 +266,7 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String @Override public VirtualMachineEntity createVirtualMachineFromScratch(String id, String owner, String isoId, String hostName, String displayName, String hypervisor, String os, int cpu, int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map> networkNicMap, DeploymentPlan plan, - Map> extraDhcpOptionMap, Long diskOfferingId) + Map> extraDhcpOptionMap, Long diskOfferingId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { // VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, vmEntityManager); @@ -321,7 +323,7 @@ public VirtualMachineEntity createVirtualMachineFromScratch(String id, String ow HypervisorType hypervisorType = HypervisorType.valueOf(hypervisor); - _itMgr.allocate(vm.getInstanceName(), _templateDao.findById(new Long(isoId)), computeOffering, rootDiskOfferingInfo, new ArrayList(), networkIpMap, plan, hypervisorType, extraDhcpOptionMap, null); + _itMgr.allocate(vm.getInstanceName(), _templateDao.findByIdIncludingRemoved(new Long(isoId)), computeOffering, rootDiskOfferingInfo, new ArrayList(), networkIpMap, plan, hypervisorType, extraDhcpOptionMap, null, volume, snapshot); return vmEntity; } diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index e0075888caf7..6a831b3820b8 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -898,10 +898,19 @@ public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offeri } private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm, - Account owner, long deviceId, String configurationId) { + Account owner, long deviceId, String configurationId, Volume volume, Snapshot snapshot) { assert (template.getFormat() != ImageFormat.ISO) : "ISO is not a template."; - Long size = _tmpltMgr.getTemplateSize(template, vm.getDataCenterId()); + if (volume != null) { + volume = attachExistingVolumeToVm(vm, deviceId, volume, type); + return toDiskProfile(volume, offering); + } + Long size; + if (snapshot != null) { + size = _volsDao.findByIdIncludingRemoved(snapshot.getVolumeId()).getSize(); + } else { + size = _tmpltMgr.getTemplateSize(template, vm.getDataCenterId()); + } if (rootDisksize != null) { if (template.isDeployAsIs()) { // Volume size specified from template deploy-as-is @@ -961,9 +970,32 @@ private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering _resourceLimitMgr.incrementVolumeResourceCount(vm.getAccountId(), vol.isDisplayVolume(), vol.getSize(), offering); } + if (snapshot != null) { + UserVmVO userVmVO = _userVmDao.findById(vm.getId()); + try { + VolumeInfo volumeInfo = createVolumeFromSnapshot(vol, snapshot, userVmVO); + return toDiskProfile(volumeInfo, offering); + } catch (StorageUnavailableException ex) { + throw new CloudRuntimeException("Could not create volume from a snapshot", ex); + } + } return toDiskProfile(vol, offering); } + private Volume attachExistingVolumeToVm(VirtualMachine vm, long deviceId, Volume volume, Type type) { + VolumeVO volumeVO = _volumeDao.findById(volume.getId()); + if (volumeVO == null) { + throw new CloudRuntimeException(String.format("Could not find the volume %s in the DB", volume)); + } + volumeVO.setDeviceId(deviceId); + volumeVO.setVolumeType(type); + if (vm != null) { + volumeVO.setInstanceId(vm.getId()); + } + _volumeDao.update(volumeVO.getId(), volumeVO); + return volumeVO; + } + @Override public void saveVolumeDetails(Long diskOfferingId, Long volumeId) { List volumeDetailsVO = new ArrayList<>(); @@ -993,7 +1025,7 @@ public void saveVolumeDetails(Long diskOfferingId, Long volumeId) { @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating ROOT volume", create = true) @Override public List allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm, - Account owner) { + Account owner, Volume volume, Snapshot snapshot) { String templateToString = getReflectOnlySelectedFields(template); int volumesNumber = 1; @@ -1040,7 +1072,7 @@ public List allocateTemplatedVolumes(Type type, String name, DiskOf } logger.info("Adding disk object [{}] to VM [{}]", volumeName, vm); DiskProfile diskProfile = allocateTemplatedVolume(type, volumeName, offering, volumeSize, minIops, maxIops, - template, vm, owner, deviceId, configurationId); + template, vm, owner, deviceId, configurationId, volume, snapshot); profiles.add(diskProfile); } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java index 7f7f8c07f067..5942f826e811 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java @@ -422,13 +422,13 @@ protected UserVm createKubernetesNode(String joinIp, Long domainId, Long account hostName, hostName, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs, null, addrs, null, null, Objects.nonNull(affinityGroupId) ? Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, - null, true, null, UserVmManager.CKS_NODE); + null, true, null, UserVmManager.CKS_NODE, null, null); } else { nodeVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, workerNodeTemplate, networkIds, owner, hostName, hostName, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs, null, addrs, null, null, Objects.nonNull(affinityGroupId) ? - Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null); + Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null); } if (logger.isInfoEnabled()) { logger.info("Created node VM : {}, {} in the Kubernetes cluster : {}", hostName, nodeVm, kubernetesCluster.getName()); diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java index 68bec58d4623..21db2ebb8f74 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java @@ -279,13 +279,13 @@ private Pair createKubernetesControlNode(final Network network, S hostName, hostName, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, userDataId, userDataDetails, keypairs, requestedIps, addrs, null, null, Objects.nonNull(affinityGroupId) ? Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, - null, true, null, UserVmManager.CKS_NODE); + null, true, null, UserVmManager.CKS_NODE, null, null); } else { controlVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, controlNodeTemplate, networkIds, owner, hostName, hostName, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, userDataId, userDataDetails, keypairs, requestedIps, addrs, null, null, Objects.nonNull(affinityGroupId) ? - Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null); + Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null); } if (logger.isInfoEnabled()) { logger.info("Created control VM: {}, {} in the Kubernetes cluster: {}", controlVm, hostName, kubernetesCluster); @@ -447,13 +447,13 @@ private UserVm createKubernetesAdditionalControlNode(final String joinIp, final hostName, hostName, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs, null, addrs, null, null, Objects.nonNull(affinityGroupId) ? Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, - null, true, null, UserVmManager.CKS_NODE); + null, true, null, UserVmManager.CKS_NODE, null, null); } else { additionalControlVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, controlNodeTemplate, networkIds, owner, hostName, hostName, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs, null, addrs, null, null, Objects.nonNull(affinityGroupId) ? - Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null); + Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null); } if (logger.isInfoEnabled()) { @@ -491,13 +491,13 @@ private UserVm createEtcdNode(List requestedIps, List invocation.getArgument(0)); when(internalLbVmDao.findById(anyLong())).thenReturn(mock(DomainRouterVO.class)); doThrow(new InsufficientServerCapacityException("Not enough capacity", id)) - .when(virtualMachineManager).allocate(anyString(), eq(template1), eq(serviceOffering), eq(networks), eq(plan), isNull()); + .when(virtualMachineManager).allocate(anyString(), eq(template1), eq(serviceOffering), eq(networks), eq(plan), isNull(), isNull(), isNull()); DomainRouterVO result = service.deployInternalLbVmWithTemplates(null, id, plan, internalLbProviderId, account, userId, vpcId, serviceOffering, networks, templates); assertNotNull(result); - verify(virtualMachineManager).allocate(anyString(), eq(template1), eq(serviceOffering), eq(networks), eq(plan), isNull()); - verify(virtualMachineManager).allocate(anyString(), eq(template2), eq(serviceOffering), eq(networks), eq(plan), isNull()); + verify(virtualMachineManager).allocate(anyString(), eq(template1), eq(serviceOffering), eq(networks), eq(plan), isNull(), isNull(), isNull()); + verify(virtualMachineManager).allocate(anyString(), eq(template2), eq(serviceOffering), eq(networks), eq(plan), isNull(), isNull(), isNull()); } @Test(expected = InsufficientCapacityException.class) @@ -166,7 +166,7 @@ public void testDeployInternalLbVmWithTemplates_AllTemplatesFail() throws Except LinkedHashMap> networks = new LinkedHashMap<>(); when(internalLbVmDao.persist(any(DomainRouterVO.class))).thenAnswer(invocation -> invocation.getArgument(0)); doThrow(new InsufficientServerCapacityException("Insufficient capacity", id)) - .when(virtualMachineManager).allocate(anyString(), any(VMTemplateVO.class), eq(serviceOffering), eq(networks), eq(plan), isNull()); + .when(virtualMachineManager).allocate(anyString(), any(VMTemplateVO.class), eq(serviceOffering), eq(networks), eq(plan), isNull(), isNull(), isNull()); service.deployInternalLbVmWithTemplates(null, id, plan, internalLbProviderId, account, userId, vpcId, serviceOffering, networks, templates); } } diff --git a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ServiceManagerImpl.java b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ServiceManagerImpl.java index 08941c56e3c5..ce30b2281181 100644 --- a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ServiceManagerImpl.java +++ b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ServiceManagerImpl.java @@ -130,7 +130,7 @@ private ServiceVirtualMachine createServiceVM(DataCenter zone, Account owner, Vi svm.setUserData(userData); try { - _vmManager.allocate(instanceName, template, serviceOffering, networks, plan, template.getHypervisorType()); + _vmManager.allocate(instanceName, template, serviceOffering, networks, plan, template.getHypervisorType(), null, null); } catch (InsufficientCapacityException ex) { throw new CloudRuntimeException("Insufficient capacity", ex); } diff --git a/plugins/network-elements/netscaler/src/main/java/com/cloud/network/vm/NetScalerVMManagerImpl.java b/plugins/network-elements/netscaler/src/main/java/com/cloud/network/vm/NetScalerVMManagerImpl.java index c3d4cf4b24ea..b47fc3aaf6bc 100644 --- a/plugins/network-elements/netscaler/src/main/java/com/cloud/network/vm/NetScalerVMManagerImpl.java +++ b/plugins/network-elements/netscaler/src/main/java/com/cloud/network/vm/NetScalerVMManagerImpl.java @@ -356,7 +356,7 @@ public Map deployNsVpx(Account owner, DeployDestination dest, De nsVpx = _routerDao.persist(nsVpx); VMInstanceVO vmVO= _vmDao.findVMByHostName(nxVpxName); - _itMgr.allocate(nxVpxName, template, vpxOffering, networks, plan, template.getHypervisorType()); + _itMgr.allocate(nxVpxName, template, vpxOffering, networks, plan, template.getHypervisorType(), null, null); Map params = new HashMap(1); try { if (vmVO != null) { diff --git a/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java b/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java index a78164f603dc..ef3d98354a05 100644 --- a/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java +++ b/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java @@ -17,29 +17,41 @@ package org.apache.cloudstack.storage.sharedfs.lifecycle; -import static org.apache.cloudstack.storage.sharedfs.SharedFS.SharedFSPath; -import static org.apache.cloudstack.storage.sharedfs.SharedFS.SharedFSVmNamePrefix; -import static org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider.SHAREDFSVM_MIN_CPU_COUNT; -import static org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider.SHAREDFSVM_MIN_RAM_SIZE; - +import com.cloud.dc.DataCenter; import com.cloud.dc.dao.DataCenterDao; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ManagementServerException; import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.VirtualMachineMigrationException; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.network.Network; import com.cloud.offering.ServiceOffering; +import com.cloud.resource.ResourceManager; +import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.LaunchPermissionVO; +import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeApiService; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.LaunchPermissionDao; +import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; import com.cloud.uservm.UserVm; import com.cloud.utils.FileUtil; import com.cloud.utils.Pair; - +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.UserVmManager; +import com.cloud.vm.UserVmService; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.UserVmDao; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -47,17 +59,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; - import javax.inject.Inject; - -import com.cloud.exception.ResourceUnavailableException; -import com.cloud.utils.net.NetUtils; -import com.cloud.vm.UserVmManager; -import com.cloud.vm.UserVmService; -import com.cloud.vm.UserVmVO; -import com.cloud.vm.dao.NicDao; -import com.cloud.vm.dao.UserVmDao; - import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.context.CallContext; @@ -67,17 +69,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.cloud.dc.DataCenter; -import com.cloud.hypervisor.Hypervisor; -import com.cloud.network.Network; -import com.cloud.resource.ResourceManager; -import com.cloud.service.dao.ServiceOfferingDao; -import com.cloud.storage.VMTemplateVO; -import com.cloud.storage.dao.VMTemplateDao; -import com.cloud.user.Account; -import com.cloud.user.AccountManager; -import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.vm.VirtualMachineManager; + +import static org.apache.cloudstack.storage.sharedfs.SharedFS.SharedFSPath; +import static org.apache.cloudstack.storage.sharedfs.SharedFS.SharedFSVmNamePrefix; +import static org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider.SHAREDFSVM_MIN_CPU_COUNT; +import static org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider.SHAREDFSVM_MIN_RAM_SIZE; public class StorageVmSharedFSLifeCycle implements SharedFSLifeCycle { protected Logger logger = LogManager.getLogger(getClass()); @@ -197,7 +193,7 @@ private UserVm deploySharedFSVM(Long zoneId, Account owner, List networkId diskOfferingId, size, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs, null, addrs, null, null, null, customParameterMap, null, null, null, null, - true, UserVmManager.SHAREDFSVM, null); + true, UserVmManager.SHAREDFSVM, null, null, null); vmContext.setEventResourceId(vm.getId()); userVmService.startVirtualMachine(vm, null); } catch (InsufficientCapacityException ex) { diff --git a/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java b/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java index 4393b0565f89..813d8978697c 100644 --- a/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java +++ b/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java @@ -17,39 +17,6 @@ package org.apache.cloudstack.storage.sharedfs.lifecycle; -import static org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider.SHAREDFSVM_MIN_CPU_COUNT; -import static org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider.SHAREDFSVM_MIN_RAM_SIZE; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.util.List; -import java.util.Optional; - -import org.apache.cloudstack.api.ApiCommandResourceType; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.storage.sharedfs.SharedFS; -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.MockitoAnnotations; -import org.mockito.Spy; -import org.mockito.junit.MockitoJUnitRunner; - import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; @@ -85,6 +52,38 @@ import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.sharedfs.SharedFS; +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.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + + +import static org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider.SHAREDFSVM_MIN_CPU_COUNT; +import static org.apache.cloudstack.storage.sharedfs.provider.StorageVmSharedFSProvider.SHAREDFSVM_MIN_RAM_SIZE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class StorageVmSharedFSLifeCycleTest { @@ -258,7 +257,7 @@ public void testDeploySharedFS() throws ResourceUnavailableException, Insufficie anyString(), anyLong(), anyLong(), isNull(), any(Hypervisor.HypervisorType.class), any(BaseCmd.HTTPMethod.class), anyString(), isNull(), isNull(), anyList(), isNull(), any(Network.IpAddresses.class), isNull(), isNull(), isNull(), anyMap(), isNull(), isNull(), isNull(), isNull(), - anyBoolean(), anyString(), isNull())).thenReturn(vm); + anyBoolean(), anyString(), isNull(), isNull(), isNull())).thenReturn(vm); VolumeVO volume = mock(VolumeVO.class); when(volume.getId()).thenReturn(s_volumeId); diff --git a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java index 5baed2643c93..4331cc494c45 100644 --- a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java +++ b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java @@ -725,7 +725,7 @@ protected Map createProxyInstance(long dataCenterId, List> networks = configureDefaultNics(routerDeploymentDefinition); - _itMgr.allocate(router.getInstanceName(), template, routerOffering, networks, routerDeploymentDefinition.getPlan(), hType); + _itMgr.allocate(router.getInstanceName(), template, routerOffering, networks, routerDeploymentDefinition.getPlan(), hType, null, null); } public static void setSystemAccount(final Account systemAccount) { diff --git a/server/src/main/java/com/cloud/network/router/VpcNetworkHelperImpl.java b/server/src/main/java/com/cloud/network/router/VpcNetworkHelperImpl.java index 13e118349b03..d02fff1ee9e6 100644 --- a/server/src/main/java/com/cloud/network/router/VpcNetworkHelperImpl.java +++ b/server/src/main/java/com/cloud/network/router/VpcNetworkHelperImpl.java @@ -169,7 +169,7 @@ public void reallocateRouterNetworks(final RouterDeploymentDefinition vpcRouterD } } - _itMgr.allocate(router.getInstanceName(), template, routerOffering, networks, vpcRouterDeploymentDefinition.getPlan(), hType); + _itMgr.allocate(router.getInstanceName(), template, routerOffering, networks, vpcRouterDeploymentDefinition.getPlan(), hType, null, null); } @Override diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index d08c8655458b..af6b0f260cf5 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -397,6 +397,10 @@ import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; + + public class UserVmManagerImpl extends ManagerBase implements UserVmManager, VirtualMachineGuru, Configurable { /** @@ -617,6 +621,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir @Inject NetworkService networkService; + @Inject + SnapshotDataFactory snapshotDataFactory; private ScheduledExecutorService _executor = null; private ScheduledExecutorService _vmIpFetchExecutor = null; @@ -3705,7 +3711,7 @@ public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOff Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParametes, String customId, Map> dhcpOptionMap, - Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, + Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); @@ -3754,7 +3760,7 @@ public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOff return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParametes, customId, dhcpOptionMap, - dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId); + dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId, volume, snapshot); } @@ -3764,7 +3770,7 @@ public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, Service List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, - Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, String vmType) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { + Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, String vmType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); List networkList = new ArrayList(); @@ -3867,7 +3873,7 @@ public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, Service return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, dataDiskTemplateToDiskOfferingMap, - userVmOVFProperties, dynamicScalingEnabled, vmType, overrideDiskOfferingId); + userVmOVFProperties, dynamicScalingEnabled, vmType, overrideDiskOfferingId, volume, snapshot); } @Override @@ -3876,7 +3882,7 @@ public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serv String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayvm, String keyboard, List affinityGroupIdList, Map customParametrs, String customId, Map> dhcpOptionsMap, Map dataDiskTemplateToDiskOfferingMap, - Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, + Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); @@ -3929,7 +3935,7 @@ public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serv verifyExtraDhcpOptionsNetwork(dhcpOptionsMap, networkList); return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, null, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, hypervisor, caller, requestedIps, defaultIps, displayvm, keyboard, affinityGroupIdList, customParametrs, customId, dhcpOptionsMap, - dataDiskTemplateToDiskOfferingMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, overrideDiskOfferingId); + dataDiskTemplateToDiskOfferingMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, overrideDiskOfferingId, volume, snapshot); } @Override @@ -4061,7 +4067,7 @@ private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffe Long userDataId, String userDataDetails, List sshKeyPairs, HypervisorType hypervisor, Account caller, Map requestedIps, IpAddresses defaultIps, Boolean isDisplayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map datadiskTemplateToDiskOfferringMap, - Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId) throws InsufficientCapacityException, ResourceUnavailableException, + Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, StorageUnavailableException, ResourceAllocationException { _accountMgr.checkAccess(caller, null, true, owner); @@ -4069,7 +4075,7 @@ private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffe if (owner.getState() == Account.State.DISABLED) { throw new PermissionDeniedException("The owner of vm to deploy is disabled: " + owner); } - VMTemplateVO template = _templateDao.findById(tmplt.getId()); + VMTemplateVO template = _templateDao.findByIdIncludingRemoved(tmplt.getId()); if (template != null) { _templateDao.loadDetails(template); } @@ -4141,10 +4147,18 @@ private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffe rootDiskOfferingId = overrideDiskOfferingId; } - DiskOfferingVO rootdiskOffering = _diskOfferingDao.findById(rootDiskOfferingId); - long volumesSize = configureCustomRootDiskSize(customParameters, template, hypervisorType, rootdiskOffering); + DiskOfferingVO rootDiskOffering = _diskOfferingDao.findById(rootDiskOfferingId); + long volumesSize = 0; + if (volume != null) { + volumesSize = volume.getSize(); + } else if (snapshot != null) { + VolumeVO volumeVO = _volsDao.findById(snapshot.getVolumeId()); + volumesSize = volumeVO != null ? volumeVO.getSize() : 0; + } else { + volumesSize = configureCustomRootDiskSize(customParameters, template, hypervisorType, rootDiskOffering); + } - if (rootdiskOffering.getEncrypt() && hypervisorType != HypervisorType.KVM) { + if (rootDiskOffering.getEncrypt() && hypervisorType != HypervisorType.KVM) { throw new InvalidParameterValueException("Root volume encryption is not supported for hypervisor type " + hypervisorType); } @@ -4153,7 +4167,7 @@ private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffe DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); additionalDiskSize = verifyAndGetDiskSize(diskOffering, diskSize); } - UserVm vm = getCheckedUserVmResource(zone, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, caller, requestedIps, defaultIps, isDisplayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, template, hypervisorType, accountId, offering, isIso, rootDiskOfferingId, volumesSize, additionalDiskSize); + UserVm vm = getCheckedUserVmResource(zone, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, caller, requestedIps, defaultIps, isDisplayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, template, hypervisorType, accountId, offering, isIso, rootDiskOfferingId, volumesSize, additionalDiskSize, volume, snapshot); _securityGroupMgr.addInstanceToGroups(vm, securityGroupIdList); @@ -4173,14 +4187,14 @@ private UserVm getCheckedUserVmResource(DataCenter zone, String hostName, String Map> dhcpOptionMap, Map datadiskTemplateToDiskOfferringMap, Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String vmType, VMTemplateVO template, HypervisorType hypervisorType, long accountId, ServiceOfferingVO offering, boolean isIso, - Long rootDiskOfferingId, long volumesSize, long additionalDiskSize) throws ResourceAllocationException { + Long rootDiskOfferingId, long volumesSize, long additionalDiskSize, Volume volume, Snapshot snapshot) throws ResourceAllocationException { if (!VirtualMachineManager.ResourceCountRunningVMsonly.value()) { List resourceLimitHostTags = resourceLimitService.getResourceLimitHostTags(offering, template); try (CheckedReservation vmReservation = new CheckedReservation(owner, ResourceType.user_vm, resourceLimitHostTags, 1l, reservationDao, resourceLimitService); CheckedReservation cpuReservation = new CheckedReservation(owner, ResourceType.cpu, resourceLimitHostTags, Long.valueOf(offering.getCpu()), reservationDao, resourceLimitService); CheckedReservation memReservation = new CheckedReservation(owner, ResourceType.memory, resourceLimitHostTags, Long.valueOf(offering.getRamSize()), reservationDao, resourceLimitService); ) { - return getUncheckedUserVmResource(zone, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, caller, requestedIps, defaultIps, isDisplayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, template, hypervisorType, accountId, offering, isIso, rootDiskOfferingId, volumesSize, additionalDiskSize); + return getUncheckedUserVmResource(zone, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, caller, requestedIps, defaultIps, isDisplayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, template, hypervisorType, accountId, offering, isIso, rootDiskOfferingId, volumesSize, additionalDiskSize, volume, snapshot); } catch (ResourceAllocationException | CloudRuntimeException e) { throw e; } catch (Exception e) { @@ -4189,7 +4203,7 @@ private UserVm getCheckedUserVmResource(DataCenter zone, String hostName, String } } else { - return getUncheckedUserVmResource(zone, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, caller, requestedIps, defaultIps, isDisplayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, template, hypervisorType, accountId, offering, isIso, rootDiskOfferingId, volumesSize, additionalDiskSize); + return getUncheckedUserVmResource(zone, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, userData, userDataId, userDataDetails, sshKeyPairs, caller, requestedIps, defaultIps, isDisplayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, template, hypervisorType, accountId, offering, isIso, rootDiskOfferingId, volumesSize, additionalDiskSize, volume, snapshot); } } @@ -4206,7 +4220,7 @@ private UserVm getUncheckedUserVmResource(DataCenter zone, String hostName, Stri Map> dhcpOptionMap, Map datadiskTemplateToDiskOfferringMap, Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String vmType, VMTemplateVO template, HypervisorType hypervisorType, long accountId, ServiceOfferingVO offering, boolean isIso, - Long rootDiskOfferingId, long volumesSize, long additionalDiskSize) throws ResourceAllocationException + Long rootDiskOfferingId, long volumesSize, long additionalDiskSize, Volume volume, Snapshot snapshot) throws ResourceAllocationException { List rootResourceLimitStorageTags = getResourceLimitStorageTags(rootDiskOfferingId != null ? rootDiskOfferingId : offering.getDiskOfferingId()); List additionalResourceLimitStorageTags = diskOfferingId != null ? getResourceLimitStorageTags(diskOfferingId) : null; @@ -4304,9 +4318,21 @@ private UserVm getUncheckedUserVmResource(DataCenter zone, String hostName, Stri if (template.getTemplateType().equals(TemplateType.SYSTEM) && !CKS_NODE.equals(vmType) && !SHAREDFSVM.equals(vmType)) { throw new InvalidParameterValueException(String.format("Unable to use system template %s to deploy a user vm", template)); } - List listZoneTemplate = _templateZoneDao.listByZoneTemplate(zone.getId(), template.getId()); - if (listZoneTemplate == null || listZoneTemplate.isEmpty()) { - throw new InvalidParameterValueException(String.format("The template %s is not available for use", template)); + + if (volume != null) { + if (zone.getId() != volume.getDataCenterId()) { + throw new InvalidParameterValueException(String.format("The volume's zone [%s] is not the same as the provided zone [%s]", volume.getDataCenterId(), zone.getId())); + } + } else if (snapshot != null) { + List snapshotsOnZone = snapshotDataFactory.getSnapshots(snapshot.getId(), zone.getId()); + if (CollectionUtils.isEmpty(snapshotsOnZone)) { + throw new InvalidParameterValueException("The snapshot does not exist on zone " + zone.getId()); + } + } else { + List listZoneTemplate = _templateZoneDao.listByZoneTemplate(zone.getId(), template.getId()); + if (listZoneTemplate == null || listZoneTemplate.isEmpty()) { + throw new InvalidParameterValueException("The template " + template.getId() + " is not available for use"); + } } if (isIso && !template.isBootable()) { @@ -4485,7 +4511,7 @@ private UserVm getUncheckedUserVmResource(DataCenter zone, String hostName, Stri UserVmVO vm = commitUserVm(zone, template, hostName, displayName, owner, diskOfferingId, diskSize, userData, userDataId, userDataDetails, caller, isDisplayVm, keyboard, accountId, userId, offering, isIso, sshPublicKeys, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, dhcpOptionMap, - datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, rootDiskOfferingId, keypairnames); + datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, vmType, rootDiskOfferingId, keypairnames, volume, snapshot); assignInstanceToGroup(group, id); return vm; @@ -4626,7 +4652,7 @@ private UserVmVO commitUserVm(final boolean isImport, final DataCenter zone, fin final long accountId, final long userId, final ServiceOffering offering, final boolean isIso, final String sshPublicKeys, final LinkedHashMap> networkNicMap, final long id, final String instanceName, final String uuidName, final HypervisorType hypervisorType, final Map customParameters, final Map> extraDhcpOptionMap, final Map dataDiskTemplateToDiskOfferingMap, - final Map userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final boolean dynamicScalingEnabled, String vmType, final Long rootDiskOfferingId, String sshkeypairs) throws InsufficientCapacityException { + final Map userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final boolean dynamicScalingEnabled, String vmType, final Long rootDiskOfferingId, String sshkeypairs, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { UserVmVO vm = new UserVmVO(id, instanceName, displayName, template.getId(), hypervisorType, template.getGuestOSId(), offering.isOfferHA(), offering.getLimitCpuUse(), owner.getDomainId(), owner.getId(), userId, offering.getId(), userData, userDataId, userDataDetails, hostName); vm.setUuid(uuidName); @@ -4744,7 +4770,7 @@ private UserVmVO commitUserVm(final boolean isImport, final DataCenter zone, fin orchestrateVirtualMachineCreate(vm, guestOSCategory, computeTags, rootDiskTags, plan, rootDiskSize, template, hostName, displayName, owner, diskOfferingId, diskSize, offering, isIso,networkNicMap, hypervisorType, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap, - rootDiskOfferingId); + rootDiskOfferingId, volume, snapshot); } CallContext.current().setEventDetails("Vm Id: " + vm.getUuid()); @@ -4777,16 +4803,16 @@ private void orchestrateVirtualMachineCreate(UserVmVO vm, GuestOSCategoryVO gues ServiceOffering offering, boolean isIso, LinkedHashMap> networkNicMap, HypervisorType hypervisorType, Map> extraDhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, - Long rootDiskOfferingId) throws InsufficientCapacityException{ + Long rootDiskOfferingId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException{ try { if (isIso) { _orchSrvc.createVirtualMachineFromScratch(vm.getUuid(), Long.toString(owner.getAccountId()), vm.getIsoId().toString(), hostName, displayName, hypervisorType.name(), guestOSCategory.getName(), offering.getCpu(), offering.getSpeed(), offering.getRamSize(), diskSize, computeTags, rootDiskTags, - networkNicMap, plan, extraDhcpOptionMap, rootDiskOfferingId); + networkNicMap, plan, extraDhcpOptionMap, rootDiskOfferingId, volume, snapshot); } else { _orchSrvc.createVirtualMachine(vm.getUuid(), Long.toString(owner.getAccountId()), Long.toString(template.getId()), hostName, displayName, hypervisorType.name(), offering.getCpu(), offering.getSpeed(), offering.getRamSize(), diskSize, computeTags, rootDiskTags, networkNicMap, plan, rootDiskSize, extraDhcpOptionMap, - dataDiskTemplateToDiskOfferingMap, diskOfferingId, rootDiskOfferingId); + dataDiskTemplateToDiskOfferingMap, diskOfferingId, rootDiskOfferingId, volume, snapshot); } if (logger.isDebugEnabled()) { @@ -4908,13 +4934,13 @@ private UserVmVO commitUserVm(final DataCenter zone, final VirtualMachineTemplat final long accountId, final long userId, final ServiceOfferingVO offering, final boolean isIso, final String sshPublicKeys, final LinkedHashMap> networkNicMap, final long id, final String instanceName, final String uuidName, final HypervisorType hypervisorType, final Map customParameters, final Map> extraDhcpOptionMap, final Map dataDiskTemplateToDiskOfferingMap, - Map userVmOVFPropertiesMap, final boolean dynamicScalingEnabled, String vmType, final Long rootDiskOfferingId, String sshkeypairs) throws InsufficientCapacityException { + Map userVmOVFPropertiesMap, final boolean dynamicScalingEnabled, String vmType, final Long rootDiskOfferingId, String sshkeypairs, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { return commitUserVm(false, zone, null, null, template, hostName, displayName, owner, diskOfferingId, diskSize, userData, userDataId, userDataDetails, isDisplayVm, keyboard, accountId, userId, offering, isIso, sshPublicKeys, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap, - userVmOVFPropertiesMap, null, dynamicScalingEnabled, vmType, rootDiskOfferingId, sshkeypairs); + userVmOVFPropertiesMap, null, dynamicScalingEnabled, vmType, rootDiskOfferingId, sshkeypairs, volume, snapshot); } public void validateRootDiskResize(final HypervisorType hypervisorType, Long rootDiskSize, VMTemplateVO templateVO, UserVmVO vm, final Map customParameters) throws InvalidParameterValueException @@ -6134,11 +6160,40 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE } } + Account caller = CallContext.current().getCallingAccount(); + Long callerId = caller.getId(); + Long templateId = cmd.getTemplateId(); + VolumeInfo volume = null; + SnapshotVO snapshot = null; + + if (cmd.getVolumeId() != null) { + volume = getVolume(cmd.getVolumeId(), templateId, false); + if (volume == null) { + throw new InvalidParameterValueException("Could not find volume with id=" + cmd.getVolumeId()); + } + _accountMgr.checkAccess(caller, null, true, volume); + templateId = volume.getTemplateId(); + overrideDiskOfferingId = volume.getDiskOfferingId(); + } else if (cmd.getSnapshotId() != null) { + snapshot = _snapshotDao.findById(cmd.getSnapshotId()); + if (snapshot == null) { + throw new InvalidParameterValueException("Could not find snapshot with id=" + cmd.getSnapshotId()); + } + _accountMgr.checkAccess(caller, null, true, snapshot); + VolumeInfo volumeOfSnapshot = getVolume(snapshot.getVolumeId(), templateId, true); + templateId = volumeOfSnapshot.getTemplateId(); + overrideDiskOfferingId = volumeOfSnapshot.getDiskOfferingId(); + } boolean dynamicScalingEnabled = cmd.isDynamicScalingEnabled(); - VirtualMachineTemplate template = _entityMgr.findById(VirtualMachineTemplate.class, templateId); + VirtualMachineTemplate template = null; + if (volume != null || snapshot != null) { + template = _entityMgr.findByIdIncludingRemoved(VirtualMachineTemplate.class, templateId); + } else { + template = _entityMgr.findById(VirtualMachineTemplate.class, templateId); + } // Make sure a valid template ID was specified if (template == null) { throw new InvalidParameterValueException("Unable to use template " + templateId); @@ -6149,6 +6204,10 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE throw new InvalidParameterValueException("Can't deploy VNF appliance from a non-VNF template"); } + if (cmd.isVolumeOrSnapshotProvided() && + (!(HypervisorType.KVM.equals(template.getHypervisorType()) || HypervisorType.KVM.equals(cmd.getHypervisor())))) { + throw new InvalidParameterValueException("Deploying a virtual machine with existing volume/snapshot is supported only from KVM hypervisors"); + } ServiceOfferingJoinVO svcOffering = serviceOfferingJoinDao.findById(serviceOfferingId); if (template.isDeployAsIs()) { @@ -6208,9 +6267,6 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE userData = finalizeUserData(userData, userDataId, template); userData = userDataManager.validateUserData(userData, cmd.getHttpMethod()); - Account caller = CallContext.current().getCallingAccount(); - Long callerId = caller.getId(); - boolean isRootAdmin = _accountService.isRootAdmin(callerId); Long hostId = cmd.getHostId(); @@ -6237,7 +6293,7 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE vm = createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(cmd, zone, template, owner), owner, name, displayName, diskOfferingId, size , group , cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm , keyboard , cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), - dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId); + dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId, volume, snapshot); } } else { if (_networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds, @@ -6245,7 +6301,7 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE vm = createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, getSecurityGroupIdList(cmd, zone, template, owner), owner, name, displayName, diskOfferingId, size, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), - dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId, null); + dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId, null, volume, snapshot); } else { if (cmd.getSecurityGroupIdList() != null && !cmd.getSecurityGroupIdList().isEmpty()) { @@ -6253,7 +6309,7 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE } vm = createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, name, displayName, diskOfferingId, size, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), - cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId); + cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId, volume, snapshot); if (cmd instanceof DeployVnfApplianceCmd) { vnfTemplateManager.createIsolatedNetworkRulesForVnfAppliance(zone, template, owner, vm, (DeployVnfApplianceCmd) cmd); } @@ -6402,6 +6458,30 @@ protected void addLeaseDetailsForInstance(UserVm vm, Integer leaseDuration, VMLe logger.debug("Instance lease for instanceId: {} is configured to expire on: {} with action: {}", vm.getUuid(), formattedLeaseExpiryDate, leaseExpiryAction); } + 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())) { + throw new InvalidParameterValueException("Deployment of virtual machine is supported only for Zone-wide storage pools"); + } + checkIfVolumeTemplateIsTheSameAsTheProvided(volume, templateId); + if (volume.getInstanceId() != null && !isSnapshot) { + throw new InvalidParameterValueException(String.format("The volume %s is already attached to a VM %s", volume, volume.getInstanceId())); + } + } + return volume; + } + + private void checkIfVolumeTemplateIsTheSameAsTheProvided(VolumeInfo volume, Long templateId) { + if (volume.getTemplateId() != null) { + if (templateId != null && !volume.getTemplateId().equals(templateId)) { + throw new InvalidParameterValueException(String.format("The volume's template %s is not the same as the provided one %s", volume.getTemplateId(), templateId)); + } + } else { + throw new InvalidParameterValueException("The provided volume/snapshot doesn't have a template to deploy a VM"); + } + } + /** * Persist extra configuration data in the user_vm_details table as key/value pair * @param decodedUrl String consisting of the extra config data to appended onto the vmx file for VMware instances @@ -9193,7 +9273,7 @@ public UserVm importVM(final DataCenter zone, final Host host, final VirtualMach null, null, userData, null, null, isDisplayVm, keyboard, accountId, userId, serviceOffering, template.getFormat().equals(ImageFormat.ISO), sshPublicKeys, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, - null, null, null, powerState, dynamicScalingEnabled, null, serviceOffering.getDiskOfferingId(), null); + null, null, null, powerState, dynamicScalingEnabled, null, serviceOffering.getDiskOfferingId(), null, null, null); } @Override diff --git a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java index 7d2b35361bca..6b169a89267b 100644 --- a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java +++ b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java @@ -1273,7 +1273,7 @@ public void testCreateNewVM1() throws ResourceUnavailableException, Insufficient when(zoneMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); when(userVmService.createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true), any(), any(), any(), - any(), any(), any(), any(), eq(true), any())).thenReturn(userVmMock); + any(), any(), any(), any(), eq(true), any(), any(), any())).thenReturn(userVmMock); UserVm result = autoScaleManagerImplSpy.createNewVM(asVmGroupMock); @@ -1284,7 +1284,7 @@ public void testCreateNewVM1() throws ResourceUnavailableException, Insufficient Mockito.verify(userVmService).createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), matches(vmHostNamePattern), matches(vmHostNamePattern), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true), any(), any(), any(), - any(), any(), any(), any(), eq(true), any()); + any(), any(), any(), any(), eq(true), any(), any(), any()); Mockito.verify(asVmGroupMock).setNextVmSeq(nextVmSeq + 1); } @@ -1320,7 +1320,7 @@ public void testCreateNewVM2() throws ResourceUnavailableException, Insufficient when(zoneMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced); when(userVmService.createAdvancedSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), any(), any(), any(), - any(), any(), any(), any(), any(), eq(true), any(), any())).thenReturn(userVmMock); + any(), any(), any(), any(), any(), eq(true), any(), any(), any(), any())).thenReturn(userVmMock); when(networkModel.checkSecurityGroupSupportForNetwork(account, zoneMock, List.of(networkId), Collections.emptyList())).thenReturn(true); @@ -1333,7 +1333,7 @@ public void testCreateNewVM2() throws ResourceUnavailableException, Insufficient Mockito.verify(userVmService).createAdvancedSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), matches(vmHostNamePattern), matches(vmHostNamePattern), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), any(), any(), any(), - any(), any(), any(), any(), any(), eq(true), any(), any()); + any(), any(), any(), any(), any(), eq(true), any(), any(), any(), any()); Mockito.verify(asVmGroupMock).setNextVmSeq(nextVmSeq + 2); } @@ -1369,7 +1369,7 @@ public void testCreateNewVM3() throws ResourceUnavailableException, Insufficient when(zoneMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced); when(userVmService.createAdvancedVirtualMachine(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true), any(), any(), any(), - any(), any(), any(), any(), eq(true), any(), any())).thenReturn(userVmMock); + any(), any(), any(), any(), eq(true), any(), any(), any(), any())).thenReturn(userVmMock); when(networkModel.checkSecurityGroupSupportForNetwork(account, zoneMock, List.of(networkId), Collections.emptyList())).thenReturn(false); @@ -1382,7 +1382,7 @@ public void testCreateNewVM3() throws ResourceUnavailableException, Insufficient Mockito.verify(userVmService).createAdvancedVirtualMachine(any(), any(), any(), any(), any(), matches(vmHostNamePattern), matches(vmHostNamePattern), any(), any(), any(), any(), any(), eq(userData), eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true), any(), any(), any(), - any(), any(), any(), any(), eq(true), any(), any()); + any(), any(), any(), any(), eq(true), any(), any(), any(), any()); Mockito.verify(asVmGroupMock).setNextVmSeq(nextVmSeq + 3); } diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index cce30e9efcf4..b65b4513325c 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -54,6 +54,8 @@ import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.Scope; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.template.VnfTemplateManager; @@ -156,7 +158,9 @@ import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; + import org.apache.cloudstack.vm.lease.VMLeaseManager; + import org.mockito.MockedStatic; import java.text.SimpleDateFormat; @@ -174,6 +178,9 @@ import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.verify; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; + @RunWith(MockitoJUnitRunner.class) public class UserVmManagerImplTest { @@ -382,11 +389,28 @@ public class UserVmManagerImplTest { @Mock StorageManager storageManager; + @Mock + private VolumeDataFactory volumeDataFactory; + + @Mock + private VolumeInfo volumeInfo; + + @Mock + private SnapshotVO snapshotMock; + + @Mock + private PrimaryDataStore primaryDataStore; + + @Mock + private Scope scopeMock; + private static final long vmId = 1l; private static final long zoneId = 2L; private static final long accountId = 3L; private static final long serviceOfferingId = 10L; private static final long templateId = 11L; + private static final long volumeId = 1L; + private static final long snashotId = 1L; private static final long GiB_TO_BYTES = 1024 * 1024 * 1024; @@ -1100,14 +1124,14 @@ public void createVirtualMachine() throws ResourceUnavailableException, Insuffic 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(), nullable(Boolean.class), any(), any(), any(), - any(), any(), any(), any(), eq(true), any()); + any(), any(), any(), any(), eq(true), any(), any(), any()); UserVm result = userVmManagerImpl.createVirtualMachine(deployVMCmd); assertEquals(userVmVoMock, result); Mockito.verify(vnfTemplateManager).validateVnfApplianceNics(templateMock, null); Mockito.verify(userVmManagerImpl).createBasicSecurityGroupVirtualMachine(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(), any(), any(), eq(true), any(), any(), any()); } private List mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(int localVolumes, int nonLocalVolumes) { @@ -1360,7 +1384,7 @@ public void createVirtualMachineWithCloudRuntimeException() throws ResourceUnava Mockito.doThrow(cre).when(userVmManagerImpl).createBasicSecurityGroupVirtualMachine(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(), any(), any(), eq(true), any(), any(), any()); CloudRuntimeException creThrown = assertThrows(CloudRuntimeException.class, () -> userVmManagerImpl.createVirtualMachine(deployVMCmd)); ArrayList proxyIdList = creThrown.getIdProxyList(); @@ -3210,6 +3234,7 @@ public void validateSAGsOnDestHostNullStorageAccessGroups() { @Test public void validateNullStorageAccessGroupsOnSrcHost() { + Host srcHost = Mockito.mock(Host.class); Host destHost = Mockito.mock(Host.class); @@ -3390,9 +3415,84 @@ String getLeaseExpiryDate(long leaseDuration) { } Map getLeaseDetails(int leaseDuration, String leaseExecution) { + Map leaseDetails = new HashMap<>(); leaseDetails.put(VmDetailConstants.INSTANCE_LEASE_EXPIRY_DATE, getLeaseExpiryDate(leaseDuration)); leaseDetails.put(VmDetailConstants.INSTANCE_LEASE_EXECUTION, leaseExecution); return leaseDetails; } + + @Test + public void createVirtualMachineWithExistingVolume() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + 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(entityManager.findById(DiskOffering.class, serviceOffering.getId())).thenReturn(smallerDisdkOffering); + when(entityManager.findByIdIncludingRemoved(VirtualMachineTemplate.class, templateId)).thenReturn(templateMock); + when(volumeDataFactory.getVolume(volumeId)).thenReturn(volumeInfo); + 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); + when(templateMock.getFormat()).thenReturn(Storage.ImageFormat.QCOW2); + when(templateMock.getUserDataId()).thenReturn(null); + Mockito.doNothing().when(vnfTemplateManager).validateVnfApplianceNics(any(), nullable(List.class)); + when(_dcMock.isLocalStorageEnabled()).thenReturn(false); + 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(), nullable(Boolean.class), any(), any(), any(), + any(), any(), any(), any(), eq(true), any(), any(), any()); + + + userVmManagerImpl.createVirtualMachine(deployVMCmd); + } + + @Test + public void createVirtualMachineWithExistingSnapshot() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + DeployVMCmd deployVMCmd = new DeployVMCmd(); + 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(entityManager.findById(DiskOffering.class, serviceOffering.getId())).thenReturn(smallerDisdkOffering); + when(snapshotDaoMock.findById(snashotId)).thenReturn(snapshotMock); + when(entityManager.findByIdIncludingRemoved(VirtualMachineTemplate.class, templateId)).thenReturn(templateMock); + when(volumeDataFactory.getVolume(volumeId)).thenReturn(volumeInfo); + 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); + when(templateMock.getFormat()).thenReturn(Storage.ImageFormat.QCOW2); + when(templateMock.getUserDataId()).thenReturn(null); + Mockito.doNothing().when(vnfTemplateManager).validateVnfApplianceNics(any(), nullable(List.class)); + when(_dcMock.isLocalStorageEnabled()).thenReturn(false); + 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(), nullable(Boolean.class), any(), any(), any(), + any(), any(), any(), any(), eq(true), any(), any(), any()); + + + userVmManagerImpl.createVirtualMachine(deployVMCmd); + } } diff --git a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java index 25d43388b659..3ac24d3985ae 100644 --- a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java +++ b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java @@ -693,7 +693,7 @@ protected Map createSecStorageVmInstance(long dataCenterId, Seco secStorageVm = createOrUpdateSecondaryStorageVm(secStorageVm, dataCenterId, id, name, serviceOffering, template, systemAcct, role); try { - _itMgr.allocate(name, template, serviceOffering, networks, plan, template.getHypervisorType()); + _itMgr.allocate(name, template, serviceOffering, networks, plan, template.getHypervisorType(), null, null); secStorageVm = _secStorageVmDao.findById(secStorageVm.getId()); _itMgr.checkDeploymentPlan(secStorageVm, template, serviceOffering, systemAcct, plan); break; diff --git a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py new file mode 100644 index 000000000000..f542b650c384 --- /dev/null +++ b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py @@ -0,0 +1,278 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +""" BVT tests for Virtual Machine Life Cycle +""" +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase + +from marvin.lib.utils import * + +from marvin.lib.base import (Account, + Role, + ServiceOffering, + VirtualMachine, + Host, + StoragePool, + Volume, + DiskOffering, + Snapshot, + Template) +from marvin.lib.common import (get_domain, + get_zone, + get_template, + list_hosts, + list_volumes) +from marvin.codes import FAILED, PASS +from nose.plugins.attrib import attr + +import uuid + + +class TestDeployVMFromSnapshotOrVolume(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestDeployVMFromSnapshotOrVolume, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + cls.hypervisor = testClient.getHypervisorInfo() + + cls.template = get_template( + cls.apiclient, + cls.zone.id, + account="system" + ) + if cls.template == FAILED: + assert False, "get_template failed to return template with description [system]" + + cls.services["small"]["zoneid"] = cls.zone.id + + cls._cleanup = [] + + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + cls.debug(cls.account.id) + + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"] + ) + cls._cleanup.append(cls.service_offering) + + cls.virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["small"], + accountid=cls.account.name, + domainid=cls.account.domainid, + templateid=cls.template.id, + serviceofferingid=cls.service_offering.id, + mode=cls.services['mode'] + ) + volume = list_volumes( + cls.apiclient, + virtualmachineid=cls.virtual_machine.id, + type='ROOT', + listall=True + )[0] + cls.snapshot = Snapshot.create( + cls.apiclient, + volume.id, + ) + + @classmethod + def tearDownClass(cls): + super(TestDeployVMFromSnapshotOrVolume, cls).tearDownClass() + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + + def tearDown(self): + super(TestDeployVMFromSnapshotOrVolume, self).tearDown() + + @attr(tags=["advanced"], required_hardware="false") + def test_01_deploy_vm_with_existing_volume(self): + ''' + Deploy a Virtual machine with existing volume + ''' + self.create_volume_from_snapshot_deploy_vm(self.snapshot.id) + + @attr(tags=["advanced"], required_hardware="false") + def test_02_deploy_vm_with_existing_snapshot(self): + ''' + Deploy a Virtual machine with existing snapshot + ''' + + self.deploy_vm_from_snapshot(self.snapshot) + + @attr(tags=["advanced"], required_hardware="false") + def test_03_deploy_vm_with_existing_volume_deleted_template(self): + ''' + Deploy a Virtual machine with existing ROOT volume created from a templated which was deleted + ''' + services = {"displaytext": "Template-1", "name": "Template-1-name", "ostypeid": self.template.ostypeid, + "ispublic": "true"} + + template = Template.create_from_snapshot(self.apiclient, self.snapshot, services) + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "Test-%s" % uuid.uuid4()}, + accountid=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id, + serviceofferingid=self.service_offering.id, + templateid=template.id, + mode="basic", + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) + + root_volume = list_volumes( + self.apiclient, + virtualmachineid=virtual_machine.id, + type='ROOT', + listall=True + )[0] + snapshot = Snapshot.create( + self.apiclient, + root_volume.id, + ) + VirtualMachine.delete(virtual_machine, self.apiclient, expunge=True) + Template.delete(template, self.apiclient, self.zone.id) + self.create_volume_from_snapshot_deploy_vm(snapshot.id) + + @attr(tags=["advanced"], required_hardware="false") + def test_04_deploy_vm_with_existing_snapshot_deleted_template(self): + ''' + Deploy a Virtual machine with existing snapshot of a ROOT volume created from a templated which was deleted + ''' + services = {"displaytext": "Template-1", "name": "Template-1-name", "ostypeid": self.template.ostypeid, + "ispublic": "true"} + + template = Template.create_from_snapshot(self.apiclient, self.snapshot, services) + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "Test-%s" % uuid.uuid4()}, + accountid=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id, + serviceofferingid=self.service_offering.id, + templateid=template.id, + mode="basic", + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) + + root_volume = list_volumes( + self.apiclient, + virtualmachineid=virtual_machine.id, + type='ROOT', + listall=True + )[0] + snapshot = Snapshot.create( + self.apiclient, + root_volume.id, + ) + VirtualMachine.delete(virtual_machine, self.apiclient, expunge=True) + Template.delete(template, self.apiclient, self.zone.id) + self.deploy_vm_from_snapshot(snapshot) + + @attr(tags=["advanced"], required_hardware="false") + def test_05_deploy_vm_with_existing_snapshot_deleted_volume(self): + ''' + Deploy a Virtual machine with existing snapshot of a ROOT volume which was deleted + ''' + + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "Test-%s" % uuid.uuid4()}, + accountid=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id, + serviceofferingid=self.service_offering.id, + templateid=self.template.id, + mode="basic", + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) + + root_volume = list_volumes( + self.apiclient, + virtualmachineid=virtual_machine.id, + type='ROOT', + listall=True + )[0] + snapshot = Snapshot.create( + self.apiclient, + root_volume.id, + ) + VirtualMachine.delete(virtual_machine, self.apiclient, expunge=True) + self.deploy_vm_from_snapshot(snapshot) + + def deploy_vm_from_snapshot(self, snapshot): + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "Test-%s" % uuid.uuid4()}, + accountid=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id, + serviceofferingid=self.service_offering.id, + snapshotid=snapshot.id, + mode="basic", + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) + + def create_volume_from_snapshot_deploy_vm(self, snapshotid): + volume = Volume.create_from_snapshot( + self.apiclient, + snapshot_id=snapshotid, + services=self.services["volume_from_snapshot"], + zoneid=self.zone.id, + ) + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "Test-%s" % uuid.uuid4()}, + accountid=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id, + serviceofferingid=self.service_offering.id, + volumeid=volume.id, + mode="basic", + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index ea0ba00545c5..96e9ef2f6139 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -528,7 +528,8 @@ def create(cls, apiclient, services, templateid=None, accountid=None, rootdiskcontroller=None, vpcid=None, macaddress=None, datadisktemplate_diskoffering_list={}, properties=None, nicnetworklist=None, bootmode=None, boottype=None, dynamicscalingenabled=None, userdataid=None, userdatadetails=None, extraconfig=None, size=None, overridediskofferingid=None, - leaseduration=None, leaseexpiryaction=None): + leaseduration=None, leaseexpiryaction=None, volumeid=None, snapshotid=None): + """Create the instance""" cmd = deployVirtualMachine.deployVirtualMachineCmd() @@ -698,6 +699,12 @@ def create(cls, apiclient, services, templateid=None, accountid=None, if leaseexpiryaction: cmd.leaseexpiryaction = leaseexpiryaction + if volumeid: + cmd.volumeid = volumeid + + if snapshotid: + cmd.snapshotid = snapshotid + virtual_machine = apiclient.deployVirtualMachine(cmd, method=method) if 'password' in list(virtual_machine.__dict__.keys()): @@ -1215,12 +1222,15 @@ def create_custom_disk(cls, apiclient, services, account=None, @classmethod def create_from_snapshot(cls, apiclient, snapshot_id, services, - account=None, domainid=None, projectid=None): + account=None, domainid=None, projectid=None, zoneid=None): """Create Volume from snapshot""" cmd = createVolume.createVolumeCmd() cmd.name = "-".join([services["diskname"], random_gen()]) cmd.snapshotid = snapshot_id - cmd.zoneid = services["zoneid"] + if zoneid: + cmd.zoneid = zoneid + elif "zoneid" in services: + cmd.zoneid = services["zoneid"] if "size" in services: cmd.size = services["size"] if "ispublic" in services: From 6a74187156f38b810c60a3abc44db11d29234662 Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Mon, 10 Mar 2025 16:23:08 +0200 Subject: [PATCH 2/8] smoke test changes check if the hypervisor is KVM check if the primary storage's scope is ZONE wide --- ...st_vm_lifecycle_with_snapshot_or_volume.py | 52 ++++++++++++++++--- tools/marvin/marvin/lib/base.py | 7 ++- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py index f542b650c384..c7bd45f633e9 100644 --- a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py +++ b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py @@ -35,7 +35,8 @@ get_zone, get_template, list_hosts, - list_volumes) + list_volumes, + list_storage_pools) from marvin.codes import FAILED, PASS from nose.plugins.attrib import attr @@ -56,6 +57,10 @@ def setUpClass(cls): cls.services['mode'] = cls.zone.networktype cls.hypervisor = testClient.getHypervisorInfo() + if cls.hypervisor.lower() != "kvm": + cls.unsupportedHypervisor = True + return + cls.template = get_template( cls.apiclient, cls.zone.id, @@ -76,11 +81,44 @@ def setUpClass(cls): cls._cleanup.append(cls.account) cls.debug(cls.account.id) - cls.service_offering = ServiceOffering.create( - cls.apiclient, - cls.services["service_offerings"]["tiny"] - ) - cls._cleanup.append(cls.service_offering) + storage_pools_response = list_storage_pools(cls.apiclient, + zoneid=cls.zone.id, + scope="ZONE") + + if storage_pools_response: + cls.zone_wide_storage = storage_pools_response[0] + + cls.debug( + "zone wide storage id is %s" % + cls.zone_wide_storage.id) + update1 = StoragePool.update(cls.apiclient, + id=cls.zone_wide_storage.id, + tags="test-vm" + ) + cls.debug( + "Storage %s pool tag%s" % + (cls.zone_wide_storage.id, update1.tags)) + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["small"], + tags="test-vm" + ) + cls._cleanup.append(cls.service_offering) + + do = { + "name": "do-tags", + "displaytext": "Disk offering with tags", + "disksize":8, + "tags": "test-vm" + } + cls.disk_offering = DiskOffering.create( + cls.apiclient, + do, + ) + cls._cleanup.append(cls.disk_offering) + else: + cls.debug("No zone wide storage found") + return cls.virtual_machine = VirtualMachine.create( cls.apiclient, @@ -259,7 +297,7 @@ def create_volume_from_snapshot_deploy_vm(self, snapshotid): volume = Volume.create_from_snapshot( self.apiclient, snapshot_id=snapshotid, - services=self.services["volume_from_snapshot"], + disk_offering=self.disk_offering.id, zoneid=self.zone.id, ) virtual_machine = VirtualMachine.create(self.apiclient, diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 96e9ef2f6139..0e2e5e92da5a 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -2746,10 +2746,13 @@ def __init__(self, items): self.__dict__.update(items) @classmethod - def create(cls, apiclient, services, tags=None, custom=False, domainid=None, cacheMode=None, **kwargs): + def create(cls, apiclient, services, tags=None, custom=False, domainid=None, cacheMode=None, displaytext=None, **kwargs): """Create Disk offering""" cmd = createDiskOffering.createDiskOfferingCmd() - cmd.displaytext = services["displaytext"] + if displaytext: + cmd.displaytext = displaytext + else: + cmd.displaytext = services["displaytext"] cmd.name = services["name"] if custom: cmd.customized = True From 66bfcd3a0e40bdeddeea29e42885ab0baefef1d1 Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Wed, 12 Mar 2025 12:57:51 +0200 Subject: [PATCH 3/8] skip all tests if the storage isn't Zone-Wide and the hypervisor isn't KVM --- .../smoke/test_vm_lifecycle_with_snapshot_or_volume.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py index c7bd45f633e9..9f1211db4c4c 100644 --- a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py +++ b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py @@ -41,7 +41,7 @@ from nose.plugins.attrib import attr import uuid - +import unittest class TestDeployVMFromSnapshotOrVolume(cloudstackTestCase): @@ -58,8 +58,7 @@ def setUpClass(cls): cls.hypervisor = testClient.getHypervisorInfo() if cls.hypervisor.lower() != "kvm": - cls.unsupportedHypervisor = True - return + raise unittest.SkipTest("Only KVM hypervisor is supported for deployment of a VM with volume/snapshot") cls.template = get_template( cls.apiclient, @@ -117,8 +116,8 @@ def setUpClass(cls): ) cls._cleanup.append(cls.disk_offering) else: - cls.debug("No zone wide storage found") - return + raise unittest.SkipTest("No zone wide storage found. Skipping tests") + cls.virtual_machine = VirtualMachine.create( cls.apiclient, From 599e24bf1bf86e4433ed967287b62bf8ab08e831 Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Wed, 28 May 2025 17:08:11 +0300 Subject: [PATCH 4/8] support StorPool tags add StorPool tags to a volume created from snapshot or to a volume which will be attached as a ROOT to a new VM --- .../orchestration/VolumeOrchestrator.java | 19 +++++ .../StorPoolPrimaryDataStoreDriver.java | 29 ++++++- .../plugins/storpool/test_storpool_tiers.py | 78 ++++++++++++++++++- ...st_vm_lifecycle_with_snapshot_or_volume.py | 1 + tools/marvin/marvin/lib/base.py | 5 +- 5 files changed, 126 insertions(+), 6 deletions(-) diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index 6a831b3820b8..9592711d1ee0 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -57,6 +57,8 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; @@ -272,6 +274,9 @@ public enum UserVmCloneType { @Inject protected SnapshotHelper snapshotHelper; + @Inject + private DataStoreProviderManager dataStoreProviderMgr; + private final StateMachine2 _volStateMachine; protected List _storagePoolAllocators; @@ -903,6 +908,7 @@ private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering if (volume != null) { volume = attachExistingVolumeToVm(vm, deviceId, volume, type); + provideVmInfoToTheStorageVolume(vm, volume); return toDiskProfile(volume, offering); } Long size; @@ -982,6 +988,19 @@ private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering return toDiskProfile(vol, offering); } + private void provideVmInfoToTheStorageVolume(VirtualMachine vm, Volume volume) { + + StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId()); + if (pool != null) { + DataStoreProvider storeProvider = dataStoreProviderMgr + .getDataStoreProvider(pool.getStorageProviderName()); + DataStoreDriver storeDriver = storeProvider.getDataStoreDriver(); + if (storeDriver != null && storeDriver instanceof PrimaryDataStoreDriver && ((PrimaryDataStoreDriver) storeDriver).isVmInfoNeeded()) { + ((PrimaryDataStoreDriver) storeDriver).provideVmInfo(vm.getId(), volume.getId()); + } + } + } + private Volume attachExistingVolumeToVm(VirtualMachine vm, long deviceId, Volume volume, Type type) { VolumeVO volumeVO = _volumeDao.findById(volume.getId()); if (volumeVO == null) { diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java index d8aa9d48e7d5..19c4aa5eb15b 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java @@ -626,8 +626,11 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal VolumeInfo vinfo = (VolumeInfo)dstData; final String volumeName = vinfo.getUuid(); final Long size = vinfo.getSize(); + SpConnectionDesc conn = StorPoolUtil.getSpConnection(vinfo.getDataStore().getUuid(), vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao); - SpApiResponse resp = StorPoolUtil.volumeCreate(volumeName, snapshotName, size, null, null, "volume", sinfo.getBaseVolume().getMaxIops(), conn); + + StorPoolVolumeDef spVolume = createVolumeWithTags(sinfo, snapshotName, vinfo, volumeName, size, conn); + SpApiResponse resp = StorPoolUtil.volumeCreate(spVolume, conn); if (resp.getError() == null) { updateStoragePool(dstData.getDataStore().getId(), size); @@ -643,9 +646,10 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal SnapshotDataStoreVO snap = getSnapshotImageStoreRef(sinfo.getId(), vinfo.getDataCenterId()); SnapshotDetailsVO snapshotDetail = snapshotDetailsDao.findDetail(sinfo.getId(), StorPoolUtil.SP_DELAY_DELETE); if (snapshotDetail != null) { - err = String.format("Could not create volume from snapshot due to: %s. The snapshot was created with the delayDelete option.", resp.getError()); + answer = new Answer(cmd, false, String.format("Could not create volume from snapshot due to: %s. The snapshot was created with the delayDelete option.", resp.getError())); } else if (snap != null && StorPoolStorageAdaptor.getVolumeNameFromPath(snap.getInstallPath(), false) == null) { - SpApiResponse emptyVolumeCreateResp = StorPoolUtil.volumeCreate(volumeName, null, size, null, null, "volume", null, conn); + spVolume.setParent(null); + SpApiResponse emptyVolumeCreateResp = StorPoolUtil.volumeCreate(spVolume, conn); if (emptyVolumeCreateResp.getError() == null) { answer = createVolumeFromSnapshot(srcData, dstData, size, emptyVolumeCreateResp); } else { @@ -655,7 +659,7 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal answer = new Answer(cmd, false, String.format("The snapshot %s does not exists neither on primary, neither on secondary storage. Cannot create volume from snapshot", snapshotName)); } } else { - err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError()); + answer = new Answer(cmd, false, String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError())); } } else if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.SNAPSHOT) { SnapshotInfo sinfo = (SnapshotInfo)srcData; @@ -982,6 +986,23 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal callback.complete(res); } + private StorPoolVolumeDef createVolumeWithTags(SnapshotInfo sinfo, String snapshotName, VolumeInfo vinfo, String volumeName, Long size, SpConnectionDesc conn) { + String tier = null; + String template = null; + if (vinfo.getDiskOfferingId() != null) { + tier = getTierFromOfferingDetail(vinfo.getDiskOfferingId()); + if (tier == null) { + template = getTemplateFromOfferingDetail(vinfo.getDiskOfferingId()); + } + } + + if (template == null) { + template = conn.getTemplateName(); + } + Map tags = StorPoolHelper.addStorPoolTags(volumeName, getVMInstanceUUID(vinfo.getInstanceId()), "volume", getVcPolicyTag(vinfo.getInstanceId()), tier); + return new StorPoolVolumeDef(null, size, tags, snapshotName, sinfo.getBaseVolume().getMaxIops(), template, null, null, null); + } + private Answer createVolumeSnapshot(StorageSubSystemCommand cmd, Long size, SpConnectionDesc conn, String volName, TemplateObjectTO dstTO) { Answer answer; diff --git a/test/integration/plugins/storpool/test_storpool_tiers.py b/test/integration/plugins/storpool/test_storpool_tiers.py index 71758c24bedc..a784830d23b8 100644 --- a/test/integration/plugins/storpool/test_storpool_tiers.py +++ b/test/integration/plugins/storpool/test_storpool_tiers.py @@ -26,7 +26,9 @@ StoragePool, VirtualMachine, SecurityGroup, - ResourceDetails + ResourceDetails, + Snapshot, + Volume, ) from marvin.lib.common import (get_domain, get_template, @@ -167,6 +169,26 @@ def setUpCloudStack(cls): cls.random_data_0 = random_gen(size=100) cls.test_dir = "/tmp" cls.random_data = "random.data" + cls.virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["small"], + accountid=cls.account.name, + domainid=cls.account.domainid, + templateid=cls.template.id, + serviceofferingid=cls.service_offering.id, + overridediskofferingid=cls.disk_offerings_tier1_tags.id, + ) + + volume = list_volumes( + cls.apiclient, + virtualmachineid=cls.virtual_machine.id, + type='ROOT', + listall=True + )[0] + cls.snapshot = Snapshot.create( + cls.apiclient, + volume.id, + ) return @classmethod @@ -437,6 +459,22 @@ def test_12_shrink_data_volume(self): disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_13_deploy_vm_from_volume_check_tags(self): + vm = self.deploy_vm_from_snapshot_or_template(snapshotid=self.snapshot.id, is_snapshot=False) + root_volume = list_volumes(self.apiclient, virtualmachineid=vm.id, type="ROOT", + listall=True) + self.vc_policy_tags(volumes=root_volume, vm=vm, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_14_deploy_vm_from_snapshot_check_tags(self): + vm = self.deploy_vm_from_snapshot_or_template(snapshotid=self.snapshot.id, is_snapshot=True) + root_volume = list_volumes(self.apiclient, virtualmachineid=vm.id, type="ROOT", + listall=True) + self.vc_policy_tags(volumes=root_volume, vm=vm, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + def deploy_vm_and_check_tier_tag(self): virtual_machine_tier1_tag = VirtualMachine.create( self.apiclient, @@ -542,3 +580,41 @@ def changeOfferingForVolume(self, volume_id, disk_offering_id, size, shrinkok=No change_offering_for_volume_cmd.shrinkok = shrinkok return self.apiclient.changeOfferingForVolume(change_offering_for_volume_cmd) + + def deploy_vm_from_snapshot_or_template(self, snapshotid, is_snapshot=False): + if is_snapshot: + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "StorPool-%s" % uuid.uuid4()}, + zoneid=self.zone.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + snapshotid=snapshotid, + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) + + return virtual_machine + volume = Volume.create_from_snapshot( + self.apiclient, + snapshot_id=snapshotid, + services=self.services, + disk_offering=self.disk_offering.id, + zoneid=self.zone.id, + ) + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "StorPool-%s" % uuid.uuid4()}, + zoneid=self.zone.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + volumeid=volume.id, + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) \ No newline at end of file diff --git a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py index 9f1211db4c4c..04f8c5db9dc6 100644 --- a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py +++ b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py @@ -296,6 +296,7 @@ def create_volume_from_snapshot_deploy_vm(self, snapshotid): volume = Volume.create_from_snapshot( self.apiclient, snapshot_id=snapshotid, + services=self.services, disk_offering=self.disk_offering.id, zoneid=self.zone.id, ) diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 0e2e5e92da5a..f74817ada54a 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -1222,7 +1222,7 @@ def create_custom_disk(cls, apiclient, services, account=None, @classmethod def create_from_snapshot(cls, apiclient, snapshot_id, services, - account=None, domainid=None, projectid=None, zoneid=None): + account=None, domainid=None, projectid=None, zoneid=None, disk_offering=None): """Create Volume from snapshot""" cmd = createVolume.createVolumeCmd() cmd.name = "-".join([services["diskname"], random_gen()]) @@ -1249,6 +1249,9 @@ def create_from_snapshot(cls, apiclient, snapshot_id, services, if projectid: cmd.projectid = projectid + if disk_offering: + cmd.diskofferingid = disk_offering + return Volume(apiclient.createVolume(cmd).__dict__) @classmethod From c5f8de30a468ad77d3c09ec0f54112c27abb2b73 Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Mon, 9 Jun 2025 12:29:57 +0300 Subject: [PATCH 5/8] Add StorPool tags on the new ROOT volume Add the StorPool's tags when volume is created from a snapshot or a volume is attached as a ROOT to a VM Addressed reviews --- .../api/command/user/vm/DeployVMCmd.java | 7 ++- .../orchestration/CloudOrchestrator.java | 9 +++- .../StorPoolPrimaryDataStoreDriver.java | 54 ++++++++++++------- .../plugins/storpool/test_storpool_tiers.py | 29 ++++++++-- ...st_vm_lifecycle_with_snapshot_or_volume.py | 2 + tools/marvin/marvin/lib/base.py | 5 +- 6 files changed, 78 insertions(+), 28 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 551570016383..745b40713db5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -36,6 +36,8 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VmDetailConstants; +import java.util.Objects; +import java.util.stream.Stream; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ACL; @@ -861,9 +863,10 @@ public void execute() { @Override public void create() throws ResourceAllocationException { - if (!isVolumeOrSnapshotProvided() && templateId == null) { - throw new CloudRuntimeException("There isn't a ROOT volume, snapshot of ROOT volume or template provided to deploy a Virtual machine"); + if (Stream.of(templateId, snapshotId, volumeId).filter(Objects::nonNull).count() != 1) { + throw new CloudRuntimeException(String.format("Only one of the parameters - template ID [%s], volume ID [%s] or snapshot ID [%s] - should be provided to deploy a Virtual machine", templateId, volumeId, snapshotId)); } + try { UserVm vm = _userVmService.createVirtualMachine(this); diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java index f6fe76611558..dab06ae995bf 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java @@ -20,6 +20,7 @@ import com.cloud.storage.Snapshot; import com.cloud.storage.Volume; +import com.cloud.template.VirtualMachineTemplate; import java.net.URL; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -256,8 +257,12 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String } } } - - _itMgr.allocate(vm.getInstanceName(), _templateDao.findByIdIncludingRemoved(new Long(templateId)), computeOffering, rootDiskOfferingInfo, dataDiskOfferings, networkIpMap, plan, + VirtualMachineTemplate template = null; + if (volume != null || snapshot != null) { + template = _templateDao.findByIdIncludingRemoved(new Long(templateId)); + } else + template = _templateDao.findById(new Long(templateId)); + _itMgr.allocate(vm.getInstanceName(), template, computeOffering, rootDiskOfferingInfo, dataDiskOfferings, networkIpMap, plan, hypervisorType, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap, volume, snapshot); return vmEntity; diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java index 19c4aa5eb15b..d93990ee0711 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java @@ -37,6 +37,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -171,6 +172,8 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { private ServiceOfferingDetailsDao serviceOfferingDetailDao; @Inject private ServiceOfferingDao serviceOfferingDao; + @Inject + private VolumeDataFactory volumeDataFactory; private SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneId) { List snaps = snapshotDataStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image); @@ -987,6 +990,12 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal } private StorPoolVolumeDef createVolumeWithTags(SnapshotInfo sinfo, String snapshotName, VolumeInfo vinfo, String volumeName, Long size, SpConnectionDesc conn) { + Pair templateAndTier = getTemplateAndTier(vinfo, conn); + Map tags = StorPoolHelper.addStorPoolTags(volumeName, getVMInstanceUUID(vinfo.getInstanceId()), "volume", getVcPolicyTag(vinfo.getInstanceId()), templateAndTier.first()); + return new StorPoolVolumeDef(null, size, tags, snapshotName, sinfo.getBaseVolume().getMaxIops(), templateAndTier.second(), null, null, null); + } + + private Pair getTemplateAndTier(VolumeInfo vinfo, SpConnectionDesc conn) { String tier = null; String template = null; if (vinfo.getDiskOfferingId() != null) { @@ -999,10 +1008,8 @@ private StorPoolVolumeDef createVolumeWithTags(SnapshotInfo sinfo, String snapsh if (template == null) { template = conn.getTemplateName(); } - Map tags = StorPoolHelper.addStorPoolTags(volumeName, getVMInstanceUUID(vinfo.getInstanceId()), "volume", getVcPolicyTag(vinfo.getInstanceId()), tier); - return new StorPoolVolumeDef(null, size, tags, snapshotName, sinfo.getBaseVolume().getMaxIops(), template, null, null, null); + return new Pair<>(tier, template); } - private Answer createVolumeSnapshot(StorageSubSystemCommand cmd, Long size, SpConnectionDesc conn, String volName, TemplateObjectTO dstTO) { Answer answer; @@ -1323,24 +1330,33 @@ public void provideVmInfo(long vmId, long volumeId) { return; } StoragePoolVO poolVO = primaryStoreDao.findById(volume.getPoolId()); - if (poolVO != null) { - try { - SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao); - String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true); - VMInstanceVO userVM = vmInstanceDao.findById(vmId); - Map tags = StorPoolHelper.addStorPoolTags(null, userVM.getUuid(), null, getVcPolicyTag(vmId), null); - if (volume.getDeviceId() != null) { - tags.put("disk", volume.getDeviceId().toString()); - } - StorPoolVolumeDef spVolume = new StorPoolVolumeDef(volName, null, tags, null, null, null, null, null, null); + if (poolVO != null && StoragePoolType.StorPool.equals(poolVO.getPoolType())) { + VolumeInfo vInfo = volumeDataFactory.getVolume(volumeId); + if (vInfo == null) { + StorPoolUtil.spLog("Could not find volume with volume ID [%s] to set tags", volumeId); + return; + } + updateVolumeWithTags(poolVO, vInfo); + } + } - SpApiResponse resp = StorPoolUtil.volumeUpdate(spVolume, conn); - if (resp.getError() != null) { - logger.warn(String.format("Could not update VC policy tags of a volume with id [%s]", volume.getUuid())); - } - } catch (Exception e) { - logger.warn(String.format("Could not update Virtual machine tags due to %s", e.getMessage())); + private void updateVolumeWithTags(StoragePoolVO poolVO, VolumeInfo vInfo) { + try { + SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao); + String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(vInfo.getPath(), true); + Pair templateAndTier = getTemplateAndTier(vInfo, conn); + Map tags = StorPoolHelper.addStorPoolTags(volName, getVMInstanceUUID(vInfo.getInstanceId()), "volume", getVcPolicyTag(vInfo.getInstanceId()), templateAndTier.first()); + if (vInfo.getDeviceId() != null) { + tags.put("disk", vInfo.getDeviceId().toString()); } + StorPoolVolumeDef spVolume = new StorPoolVolumeDef(volName, null, tags, null, null, templateAndTier.second(), null, null, null); + StorPoolUtil.spLog("Updating volume's tags [%s] with template [%s]", tags, templateAndTier.second()); + SpApiResponse resp = StorPoolUtil.volumeUpdate(spVolume, conn); + if (resp.getError() != null) { + logger.warn(String.format("Could not update VC policy tags of a volume with id [%s]", vInfo.getUuid())); + } + } catch (Exception e) { + logger.warn(String.format("Could not update Virtual machine tags due to %s", e.getMessage())); } } diff --git a/test/integration/plugins/storpool/test_storpool_tiers.py b/test/integration/plugins/storpool/test_storpool_tiers.py index a784830d23b8..00f9035c1d7b 100644 --- a/test/integration/plugins/storpool/test_storpool_tiers.py +++ b/test/integration/plugins/storpool/test_storpool_tiers.py @@ -171,12 +171,15 @@ def setUpCloudStack(cls): cls.random_data = "random.data" cls.virtual_machine = VirtualMachine.create( cls.apiclient, - cls.services["small"], + {"name": "StorPool-%s" % uuid.uuid4()}, + zoneid=cls.zone.id, + templateid=cls.template.id, accountid=cls.account.name, domainid=cls.account.domainid, - templateid=cls.template.id, serviceofferingid=cls.service_offering.id, overridediskofferingid=cls.disk_offerings_tier1_tags.id, + hypervisor=cls.hypervisor, + rootdisksize=10 ) volume = list_volumes( @@ -222,6 +225,7 @@ def tearDown(self): def test_01_check_tags_on_deployed_vm_and_datadisk(self): virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag() virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_02_change_offering_on_attached_root_disk(self): @@ -235,6 +239,7 @@ def test_02_change_offering_on_attached_root_disk(self): self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) def test_03_change_offering_on_attached_data_disk(self): virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag() @@ -247,6 +252,7 @@ def test_03_change_offering_on_attached_data_disk(self): self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_04_check_templates_on_deployed_vm_and_datadisk(self): @@ -268,6 +274,7 @@ def test_04_check_templates_on_deployed_vm_and_datadisk(self): for v in volumes: self.check_storpool_template(v, self.disk_offerings_tier1_template.id, self.spTemplate) virtual_machine_template_tier1.stop(self.apiclient, forced=True) + virtual_machine_template_tier1.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_05_check_templates_on_deployed_vm_and_datadisk_tier2(self): @@ -289,6 +296,7 @@ def test_05_check_templates_on_deployed_vm_and_datadisk_tier2(self): for v in volumes: self.check_storpool_template(v, self.disk_offerings_tier2_template.id, self.spTemplate) virtual_machine_template_tier2.stop(self.apiclient, forced=True) + virtual_machine_template_tier2.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_06_change_offerings_with_tags_detached_volume(self): @@ -322,6 +330,7 @@ def test_06_change_offerings_with_tags_detached_volume(self): self.changeOfferingForVolume(volumes[0].id, self.disk_offerings_tier1_tags.id, volumes[0].size) self.vc_policy_tags(volumes=volumes, vm=virtual_machine_tier2_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + virtual_machine_tier2_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_07_change_offerings_with_template_detached_volume(self): @@ -354,6 +363,7 @@ def test_07_change_offerings_with_template_detached_volume(self): self.changeOfferingForVolume(volumes[0].id, self.disk_offerings_tier1_template.id, volumes[0].size) self.check_storpool_template(volume=volumes[0], disk_offering_id=self.disk_offerings_tier1_template.id, qos_or_template=self.spTemplate) + virtual_machine_tier2_template.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_08_deploy_vm_with_tags_and_template_in_offerings(self): @@ -392,6 +402,7 @@ def test_08_deploy_vm_with_tags_and_template_in_offerings(self): self.changeOfferingForVolume(volumes[0].id, self.disk_offerings_tier1_tags.id, volumes[0].size) self.vc_policy_tags(volumes=volumes, vm=virtual_machine_tier2_template, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + virtual_machine_tier2_template.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_09_resize_root_volume(self): @@ -408,6 +419,7 @@ def test_09_resize_root_volume(self): self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_10_shrink_root_volume(self): @@ -425,6 +437,7 @@ def test_10_shrink_root_volume(self): listall=True) self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_11_resize_data_volume(self): @@ -441,6 +454,7 @@ def test_11_resize_data_volume(self): self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_12_shrink_data_volume(self): @@ -458,6 +472,7 @@ def test_12_shrink_data_volume(self): self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_13_deploy_vm_from_volume_check_tags(self): @@ -466,6 +481,7 @@ def test_13_deploy_vm_from_volume_check_tags(self): listall=True) self.vc_policy_tags(volumes=root_volume, vm=vm, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + vm.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_14_deploy_vm_from_snapshot_check_tags(self): @@ -474,6 +490,7 @@ def test_14_deploy_vm_from_snapshot_check_tags(self): listall=True) self.vc_policy_tags(volumes=root_volume, vm=vm, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + vm.delete(self.apiclient, expunge=True) def deploy_vm_and_check_tier_tag(self): virtual_machine_tier1_tag = VirtualMachine.create( @@ -602,8 +619,11 @@ def deploy_vm_from_snapshot_or_template(self, snapshotid, is_snapshot=False): self.apiclient, snapshot_id=snapshotid, services=self.services, - disk_offering=self.disk_offering.id, + account=self.account.name, + domainid=self.account.domainid, + disk_offering=self.disk_offerings_tier1_tags.id, zoneid=self.zone.id, + size=10 ) virtual_machine = VirtualMachine.create(self.apiclient, {"name": "StorPool-%s" % uuid.uuid4()}, @@ -617,4 +637,5 @@ def deploy_vm_from_snapshot_or_template(self, snapshotid, is_snapshot=False): ssh_client = virtual_machine.get_ssh_client() except Exception as e: self.fail("SSH failed for virtual machine: %s - %s" % - (virtual_machine.ipaddress, e)) \ No newline at end of file + (virtual_machine.ipaddress, e)) + return virtual_machine \ No newline at end of file diff --git a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py index 04f8c5db9dc6..13e823acf187 100644 --- a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py +++ b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py @@ -298,6 +298,8 @@ def create_volume_from_snapshot_deploy_vm(self, snapshotid): snapshot_id=snapshotid, services=self.services, disk_offering=self.disk_offering.id, + account=self.account.name, + domainid=self.account.domainid, zoneid=self.zone.id, ) virtual_machine = VirtualMachine.create(self.apiclient, diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index f74817ada54a..acd6a1450ed5 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -1222,7 +1222,7 @@ def create_custom_disk(cls, apiclient, services, account=None, @classmethod def create_from_snapshot(cls, apiclient, snapshot_id, services, - account=None, domainid=None, projectid=None, zoneid=None, disk_offering=None): + account=None, domainid=None, projectid=None, zoneid=None, disk_offering=None, size=None): """Create Volume from snapshot""" cmd = createVolume.createVolumeCmd() cmd.name = "-".join([services["diskname"], random_gen()]) @@ -1252,6 +1252,9 @@ def create_from_snapshot(cls, apiclient, snapshot_id, services, if disk_offering: cmd.diskofferingid = disk_offering + if size: + cmd.size = size + return Volume(apiclient.createVolume(cmd).__dict__) @classmethod From b8d9dcbc71540657276ad48cf04adc88db0b5b6f Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Mon, 16 Jun 2025 12:00:05 +0300 Subject: [PATCH 6/8] addressed review Added new line --- test/integration/plugins/storpool/test_storpool_tiers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/plugins/storpool/test_storpool_tiers.py b/test/integration/plugins/storpool/test_storpool_tiers.py index 00f9035c1d7b..f41059e92068 100644 --- a/test/integration/plugins/storpool/test_storpool_tiers.py +++ b/test/integration/plugins/storpool/test_storpool_tiers.py @@ -638,4 +638,4 @@ def deploy_vm_from_snapshot_or_template(self, snapshotid, is_snapshot=False): except Exception as e: self.fail("SSH failed for virtual machine: %s - %s" % (virtual_machine.ipaddress, e)) - return virtual_machine \ No newline at end of file + return virtual_machine From a1f75abc88317ad4b01005fd79fb6b598d316bc9 Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Fri, 27 Jun 2025 10:10:14 +0300 Subject: [PATCH 7/8] Addressed comments --- .../smoke/test_vm_lifecycle_with_snapshot_or_volume.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py index 13e823acf187..871cf25a4021 100644 --- a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py +++ b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py @@ -175,6 +175,7 @@ def test_03_deploy_vm_with_existing_volume_deleted_template(self): "ispublic": "true"} template = Template.create_from_snapshot(self.apiclient, self.snapshot, services) + self._cleanup.append(template) virtual_machine = VirtualMachine.create(self.apiclient, {"name": "Test-%s" % uuid.uuid4()}, accountid=self.account.name, @@ -201,7 +202,6 @@ def test_03_deploy_vm_with_existing_volume_deleted_template(self): root_volume.id, ) VirtualMachine.delete(virtual_machine, self.apiclient, expunge=True) - Template.delete(template, self.apiclient, self.zone.id) self.create_volume_from_snapshot_deploy_vm(snapshot.id) @attr(tags=["advanced"], required_hardware="false") @@ -213,6 +213,7 @@ def test_04_deploy_vm_with_existing_snapshot_deleted_template(self): "ispublic": "true"} template = Template.create_from_snapshot(self.apiclient, self.snapshot, services) + self._cleanup.append(template) virtual_machine = VirtualMachine.create(self.apiclient, {"name": "Test-%s" % uuid.uuid4()}, accountid=self.account.name, @@ -239,7 +240,6 @@ def test_04_deploy_vm_with_existing_snapshot_deleted_template(self): root_volume.id, ) VirtualMachine.delete(virtual_machine, self.apiclient, expunge=True) - Template.delete(template, self.apiclient, self.zone.id) self.deploy_vm_from_snapshot(snapshot) @attr(tags=["advanced"], required_hardware="false") From b36e05bd6a651386af43e73f835c85327104c49b Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Fri, 27 Jun 2025 14:19:47 +0300 Subject: [PATCH 8/8] Addressed review --- .../org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 745b40713db5..ef3eb9009709 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -864,7 +864,7 @@ public void execute() { @Override public void create() throws ResourceAllocationException { if (Stream.of(templateId, snapshotId, volumeId).filter(Objects::nonNull).count() != 1) { - throw new CloudRuntimeException(String.format("Only one of the parameters - template ID [%s], volume ID [%s] or snapshot ID [%s] - should be provided to deploy a Virtual machine", templateId, volumeId, snapshotId)); + throw new CloudRuntimeException("Please provide only one of the following parameters - template ID, volume ID or snapshot ID"); } try {