diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index a673df12d0f4..4f09702b7dba 100644 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@ -175,6 +175,8 @@ Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account acc boolean validateVolumeSizeInBytes(long size); + void validateDestroyVolume(Volume volume, Account caller, boolean expunge, boolean forceExpunge); + Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) throws ResourceAllocationException; void publishVolumeCreationUsageEvent(Volume volume); diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java index c32c099ed3a2..787ed7bde37e 100644 --- a/api/src/main/java/com/cloud/vm/UserVmService.java +++ b/api/src/main/java/com/cloud/vm/UserVmService.java @@ -492,7 +492,7 @@ UserVm moveVMToUser(AssignVMCmd moveUserVMCmd) throws ResourceAllocationExceptio UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException; - UserVm restoreVirtualMachine(Account caller, long vmId, Long newTemplateId) throws InsufficientCapacityException, ResourceUnavailableException; + UserVm restoreVirtualMachine(Account caller, long vmId, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map details) throws InsufficientCapacityException, ResourceUnavailableException; UserVm upgradeVirtualMachine(ScaleVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java index 4b59bf560cb3..17c4e97eb3b7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java @@ -16,7 +16,9 @@ // under the License. package org.apache.cloudstack.api.command.user.vm; +import com.cloud.vm.VmDetailConstants; import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.acl.SecurityChecker.AccessType; @@ -42,6 +44,8 @@ import com.cloud.uservm.UserVm; import com.cloud.vm.VirtualMachine; +import java.util.Map; + @APICommand(name = "restoreVirtualMachine", description = "Restore a VM to original template/ISO or new template/ISO", responseObject = UserVmResponse.class, since = "3.0.0", responseView = ResponseView.Restricted, entityType = {VirtualMachine.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = true) @@ -60,6 +64,28 @@ public class RestoreVMCmd extends BaseAsyncCmd implements UserCmd { description = "an optional template Id to restore vm from the new template. This can be an ISO id in case of restore vm deployed using ISO") private Long templateId; + @Parameter(name = ApiConstants.DISK_OFFERING_ID, + type = CommandType.UUID, + entityType = DiskOfferingResponse.class, + description = "Override root volume's diskoffering.", since = "4.19.1") + private Long rootDiskOfferingId; + + @Parameter(name = ApiConstants.ROOT_DISK_SIZE, + type = CommandType.LONG, + description = "Override root volume's size (in GB). Analogous to details[0].rootdisksize, which takes precedence over this parameter if both are provided", + since = "4.19.1") + private Long rootDiskSize; + + @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.19.1", + description = "used to specify the custom parameters") + private Map details; + + @Parameter(name = ApiConstants.EXPUNGE, + type = CommandType.BOOLEAN, + description = "Optional field to expunge old root volume after restore.", + since = "4.19.1") + private Boolean expungeRootDisk; + @Override public String getEventType() { return EventTypes.EVENT_VM_RESTORE; @@ -112,6 +138,22 @@ public Long getId() { return getVmId(); } + public Long getRootDiskOfferingId() { + return rootDiskOfferingId; + } + + public Map getDetails() { + Map customparameterMap = convertDetailsToMap(details); + if (rootDiskSize != null && !customparameterMap.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) { + customparameterMap.put(VmDetailConstants.ROOT_DISK_SIZE, rootDiskSize.toString()); + } + return customparameterMap; + } + + public Boolean getExpungeRootDisk() { + return expungeRootDisk != null && expungeRootDisk; + } + @Override public Long getApiResourceId() { return getId(); 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 8cd67f253310..3f7d6be6d889 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -254,7 +254,7 @@ static String getHypervisorHostname(String name) { */ boolean unmanage(String vmUuid); - UserVm restoreVirtualMachine(long vmId, Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException; + UserVm restoreVirtualMachine(long vmId, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map details) throws ResourceUnavailableException, InsufficientCapacityException; boolean checkIfVmHasClusterWideVolumes(Long vmId); 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 59d129bc065b..243613907ff4 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -5623,20 +5623,20 @@ protected void resourceCountDecrement (long accountId, Long cpu, Long memory) { } @Override - public UserVm restoreVirtualMachine(final long vmId, final Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException { + public UserVm restoreVirtualMachine(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, final Map details) throws ResourceUnavailableException, InsufficientCapacityException { final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { VmWorkJobVO placeHolder = null; placeHolder = createPlaceHolderWork(vmId); try { - return orchestrateRestoreVirtualMachine(vmId, newTemplateId); + return orchestrateRestoreVirtualMachine(vmId, newTemplateId, rootDiskOfferingId, expunge, details); } finally { if (placeHolder != null) { _workJobDao.expunge(placeHolder.getId()); } } } else { - final Outcome outcome = restoreVirtualMachineThroughJobQueue(vmId, newTemplateId); + final Outcome outcome = restoreVirtualMachineThroughJobQueue(vmId, newTemplateId, rootDiskOfferingId, expunge, details); retrieveVmFromJobOutcome(outcome, String.valueOf(vmId), "restoreVirtualMachine"); @@ -5653,14 +5653,14 @@ public UserVm restoreVirtualMachine(final long vmId, final Long newTemplateId) t } } - private UserVm orchestrateRestoreVirtualMachine(final long vmId, final Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException { - s_logger.debug("Restoring vm " + vmId + " with new templateId " + newTemplateId); + private UserVm orchestrateRestoreVirtualMachine(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, final Map details) throws ResourceUnavailableException, InsufficientCapacityException { + s_logger.debug("Restoring vm " + vmId + " with templateId : " + newTemplateId + " diskOfferingId : " + rootDiskOfferingId + " details : " + details); final CallContext context = CallContext.current(); final Account account = context.getCallingAccount(); - return _userVmService.restoreVirtualMachine(account, vmId, newTemplateId); + return _userVmService.restoreVirtualMachine(account, vmId, newTemplateId, rootDiskOfferingId, expunge, details); } - public Outcome restoreVirtualMachineThroughJobQueue(final long vmId, final Long newTemplateId) { + public Outcome restoreVirtualMachineThroughJobQueue(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, Map details) { String commandName = VmWorkRestore.class.getName(); Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); @@ -5670,7 +5670,7 @@ public Outcome restoreVirtualMachineThroughJobQueue(final long v Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); workJob = newVmWorkJobAndInfo.first(); - VmWorkRestore workInfo = new VmWorkRestore(newVmWorkJobAndInfo.second(), newTemplateId); + VmWorkRestore workInfo = new VmWorkRestore(newVmWorkJobAndInfo.second(), newTemplateId, rootDiskOfferingId, expunge, details); setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); } @@ -5682,7 +5682,7 @@ public Outcome restoreVirtualMachineThroughJobQueue(final long v @ReflectionUse private Pair orchestrateRestoreVirtualMachine(final VmWorkRestore work) throws Exception { VMInstanceVO vm = findVmById(work.getVmId()); - UserVm uservm = orchestrateRestoreVirtualMachine(vm.getId(), work.getTemplateId()); + UserVm uservm = orchestrateRestoreVirtualMachine(vm.getId(), work.getTemplateId(), work.getRootDiskOfferingId(), work.getExpunge(), work.getDetails()); HashMap passwordMap = new HashMap<>(); passwordMap.put(uservm.getId(), uservm.getPassword()); return new Pair<>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(passwordMap)); diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkRestore.java b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkRestore.java index cb3adae27aa3..ab5425a25000 100644 --- a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkRestore.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkRestore.java @@ -16,23 +16,38 @@ // under the License. package com.cloud.vm; +import java.util.Map; + public class VmWorkRestore extends VmWork { private static final long serialVersionUID = 195901782359759635L; private Long templateId; + private Long rootDiskOfferingId; + private Map details; - public VmWorkRestore(long userId, long accountId, long vmId, String handlerName, Long templateId) { - super(userId, accountId, vmId, handlerName); + private boolean expunge; - this.templateId = templateId; - } - - public VmWorkRestore(VmWork vmWork, Long templateId) { + public VmWorkRestore(VmWork vmWork, Long templateId, Long rootDiskOfferingId, boolean expunge, Map details) { super(vmWork); this.templateId = templateId; + this.rootDiskOfferingId = rootDiskOfferingId; + this.expunge = expunge; + this.details = details; } public Long getTemplateId() { return templateId; } + + public Long getRootDiskOfferingId() { + return rootDiskOfferingId; + } + + public boolean getExpunge() { + return expunge; + } + + public Map getDetails() { + return details; + } } 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 d639b4513e4a..6763a13aed63 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 @@ -61,6 +61,9 @@ import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; +import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS; +import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS; + @Component public class CloudOrchestrator implements OrchestrationService { @@ -196,8 +199,8 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String Map userVmDetails = _userVmDetailsDao.listDetailsKeyPairs(vm.getId()); if (userVmDetails != null) { - String minIops = userVmDetails.get("minIops"); - String maxIops = userVmDetails.get("maxIops"); + String minIops = userVmDetails.get(MIN_IOPS); + String maxIops = userVmDetails.get(MAX_IOPS); rootDiskOfferingInfo.setMinIops(minIops != null && minIops.trim().length() > 0 ? Long.parseLong(minIops) : null); rootDiskOfferingInfo.setMaxIops(maxIops != null && maxIops.trim().length() > 0 ? Long.parseLong(maxIops) : null); 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 3a5b342b6e85..5c79fb64d8d4 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 @@ -949,18 +949,7 @@ private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering vol = _volsDao.persist(vol); - List volumeDetailsVO = new ArrayList(); - DiskOfferingDetailVO bandwidthLimitDetail = _diskOfferingDetailDao.findDetail(offering.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS); - if (bandwidthLimitDetail != null) { - volumeDetailsVO.add(new VolumeDetailVO(vol.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS, bandwidthLimitDetail.getValue(), false)); - } - DiskOfferingDetailVO iopsLimitDetail = _diskOfferingDetailDao.findDetail(offering.getId(), Volume.IOPS_LIMIT); - if (iopsLimitDetail != null) { - volumeDetailsVO.add(new VolumeDetailVO(vol.getId(), Volume.IOPS_LIMIT, iopsLimitDetail.getValue(), false)); - } - if (!volumeDetailsVO.isEmpty()) { - _volDetailDao.saveDetails(volumeDetailsVO); - } + saveVolumeDetails(offering.getId(), vol.getId()); if (StringUtils.isNotBlank(configurationId)) { VolumeDetailVO deployConfigurationDetail = new VolumeDetailVO(vol.getId(), VmDetailConstants.DEPLOY_AS_IS_CONFIGURATION, configurationId, false); @@ -985,6 +974,32 @@ private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering return toDiskProfile(vol, offering); } + @Override + public void saveVolumeDetails(Long diskOfferingId, Long volumeId) { + List volumeDetailsVO = new ArrayList<>(); + DiskOfferingDetailVO bandwidthLimitDetail = _diskOfferingDetailDao.findDetail(diskOfferingId, Volume.BANDWIDTH_LIMIT_IN_MBPS); + if (bandwidthLimitDetail != null) { + volumeDetailsVO.add(new VolumeDetailVO(volumeId, Volume.BANDWIDTH_LIMIT_IN_MBPS, bandwidthLimitDetail.getValue(), false)); + } else { + VolumeDetailVO bandwidthLimit = _volDetailDao.findDetail(volumeId, Volume.BANDWIDTH_LIMIT_IN_MBPS); + if (bandwidthLimit != null) { + _volDetailDao.remove(bandwidthLimit.getId()); + } + } + DiskOfferingDetailVO iopsLimitDetail = _diskOfferingDetailDao.findDetail(diskOfferingId, Volume.IOPS_LIMIT); + if (iopsLimitDetail != null) { + volumeDetailsVO.add(new VolumeDetailVO(volumeId, Volume.IOPS_LIMIT, iopsLimitDetail.getValue(), false)); + } else { + VolumeDetailVO iopsLimit = _volDetailDao.findDetail(volumeId, Volume.IOPS_LIMIT); + if (iopsLimit != null) { + _volDetailDao.remove(iopsLimit.getId()); + } + } + if (!volumeDetailsVO.isEmpty()) { + _volDetailDao.saveDetails(volumeDetailsVO); + } + } + @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, diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index e5a33a228595..8679221107d8 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1724,11 +1724,7 @@ public boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitio return _volStateMachine.transitTo(vol, event, null, _volsDao); } - @Override - @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DESTROY, eventDescription = "destroying a volume") - public Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge) { - VolumeVO volume = retrieveAndValidateVolume(volumeId, caller); - + public void validateDestroyVolume(Volume volume, Account caller, boolean expunge, boolean forceExpunge) { if (expunge) { // When trying to expunge, permission is denied when the caller is not an admin and the AllowUserExpungeRecoverVolume is false for the caller. final Long userId = caller.getAccountId(); @@ -1738,6 +1734,14 @@ public Volume destroyVolume(long volumeId, Account caller, boolean expunge, bool } else if (volume.getState() == Volume.State.Allocated || volume.getState() == Volume.State.Uploaded) { throw new InvalidParameterValueException("The volume in Allocated/Uploaded state can only be expunged not destroyed/recovered"); } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DESTROY, eventDescription = "destroying a volume") + public Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge) { + VolumeVO volume = retrieveAndValidateVolume(volumeId, caller); + + validateDestroyVolume(volume, caller, expunge, forceExpunge); destroyVolumeIfPossible(volume); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index ae0d66ee482e..566fcb38fc9d 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -18,6 +18,8 @@ import static com.cloud.configuration.ConfigurationManagerImpl.VM_USERDATA_MAX_LENGTH; import static com.cloud.utils.NumbersUtil.toHumanReadableSize; +import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS; +import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS; import java.io.IOException; import java.io.StringReader; @@ -566,6 +568,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir @Inject private VmStatsDao vmStatsDao; @Inject + private DataCenterDao dataCenterDao; + @Inject private MessageBus messageBus; @Inject protected CommandSetupHelper commandSetupHelper; @@ -2148,11 +2152,11 @@ private void changeDiskOfferingForRootVolume(Long vmId, DiskOfferingVO newDiskOf Long maxIopsInNewDiskOffering = null; boolean autoMigrate = false; boolean shrinkOk = false; - if (customParameters.containsKey(ApiConstants.MIN_IOPS)) { - minIopsInNewDiskOffering = Long.parseLong(customParameters.get(ApiConstants.MIN_IOPS)); + if (customParameters.containsKey(MIN_IOPS)) { + minIopsInNewDiskOffering = Long.parseLong(customParameters.get(MIN_IOPS)); } - if (customParameters.containsKey(ApiConstants.MAX_IOPS)) { - minIopsInNewDiskOffering = Long.parseLong(customParameters.get(ApiConstants.MAX_IOPS)); + if (customParameters.containsKey(MAX_IOPS)) { + minIopsInNewDiskOffering = Long.parseLong(customParameters.get(MAX_IOPS)); } if (customParameters.containsKey(ApiConstants.AUTO_MIGRATE)) { autoMigrate = Boolean.parseBoolean(customParameters.get(ApiConstants.AUTO_MIGRATE)); @@ -3248,7 +3252,7 @@ public UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityE ServiceOfferingVO offering = serviceOfferingDao.findById(vmInstance.getId(), serviceOfferingId); if (offering != null && offering.getRemoved() == null) { if (offering.isVolatileVm()) { - return restoreVMInternal(caller, vmInstance, null); + return restoreVMInternal(caller, vmInstance); } } else { throw new InvalidParameterValueException("Unable to find service offering: " + serviceOfferingId + " corresponding to the vm"); @@ -6327,8 +6331,8 @@ protected List getSecurityGroupIdList(SecurityGroupAction cmd, DataCenter // if specified, minIops should be <= maxIops private void verifyDetails(Map details) { if (details != null) { - String minIops = details.get("minIops"); - String maxIops = details.get("maxIops"); + String minIops = details.get(MIN_IOPS); + String maxIops = details.get(MAX_IOPS); verifyMinAndMaxIops(minIops, maxIops); @@ -7660,6 +7664,20 @@ private boolean canAccountUseNetwork(Account newAccount, Network network) { return false; } + private DiskOfferingVO validateAndGetDiskOffering(Long diskOfferingId, UserVmVO vm, Account caller) { + DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); + if (diskOffering == null) { + throw new InvalidParameterValueException("Cannot find disk offering with ID " + diskOfferingId); + } + DataCenterVO zone = dataCenterDao.findById(vm.getDataCenterId()); + _accountMgr.checkAccess(caller, diskOffering, zone); + ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(vm.getServiceOfferingId()); + if (serviceOffering.getDiskOfferingStrictness() && !serviceOffering.getDiskOfferingId().equals(diskOfferingId)) { + throw new InvalidParameterValueException("VM's service offering has a strict disk offering requirement, and the specified disk offering does not match"); + } + return diskOffering; + } + @Override public UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException { // Input validation @@ -7667,6 +7685,11 @@ public UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, long vmId = cmd.getVmId(); Long newTemplateId = cmd.getTemplateId(); + Long rootDiskOfferingId = cmd.getRootDiskOfferingId(); + boolean expunge = cmd.getExpungeRootDisk(); + Map details = cmd.getDetails(); + + verifyDetails(details); UserVmVO vm = _vmDao.findById(vmId); if (vm == null) { @@ -7674,20 +7697,38 @@ public UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ex.addProxyObject(String.valueOf(vmId), "vmId"); throw ex; } - _accountMgr.checkAccess(caller, null, true, vm); + DiskOffering diskOffering = rootDiskOfferingId != null ? validateAndGetDiskOffering(rootDiskOfferingId, vm, caller) : null; + VMTemplateVO template = _templateDao.findById(newTemplateId); + if (template.getSize() != null) { + String rootDiskSize = details.get(VmDetailConstants.ROOT_DISK_SIZE); + Long templateSize = template.getSize(); + if (StringUtils.isNumeric(rootDiskSize)) { + if (Long.parseLong(rootDiskSize) * GiB_TO_BYTES < templateSize) { + throw new InvalidParameterValueException(String.format("Root disk size [%s] is smaller than the template size [%s]", rootDiskSize, templateSize)); + } + } else if (diskOffering != null && diskOffering.getDiskSize() < templateSize) { + throw new InvalidParameterValueException(String.format("Disk size for selected offering [%s] is less than the template's size [%s]", diskOffering.getDiskSize(), templateSize)); + } + } + //check if there are any active snapshots on volumes associated with the VM s_logger.debug("Checking if there are any ongoing snapshots on the ROOT volumes associated with VM with ID " + vmId); if (checkStatusOfVolumeSnapshots(vmId, Volume.Type.ROOT)) { throw new CloudRuntimeException("There is/are unbacked up snapshot(s) on ROOT volume, Re-install VM is not permitted, please try again later."); } s_logger.debug("Found no ongoing snapshots on volume of type ROOT, for the vm with id " + vmId); - return restoreVMInternal(caller, vm, newTemplateId); + return restoreVMInternal(caller, vm, newTemplateId, rootDiskOfferingId, expunge, details); } - public UserVm restoreVMInternal(Account caller, UserVmVO vm, Long newTemplateId) throws InsufficientCapacityException, ResourceUnavailableException { - return _itMgr.restoreVirtualMachine(vm.getId(), newTemplateId); + public UserVm restoreVMInternal(Account caller, UserVmVO vm, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map details) throws InsufficientCapacityException, ResourceUnavailableException { + return _itMgr.restoreVirtualMachine(vm.getId(), newTemplateId, rootDiskOfferingId, expunge, details); + } + + + public UserVm restoreVMInternal(Account caller, UserVmVO vm) throws InsufficientCapacityException, ResourceUnavailableException { + return restoreVMInternal(caller, vm, null, null, false, null); } private VMTemplateVO getRestoreVirtualMachineTemplate(Account caller, Long newTemplateId, List rootVols, UserVmVO vm) { @@ -7732,7 +7773,9 @@ private VMTemplateVO getRestoreVirtualMachineTemplate(Account caller, Long newTe } @Override - public UserVm restoreVirtualMachine(final Account caller, final long vmId, final Long newTemplateId) throws InsufficientCapacityException, ResourceUnavailableException { + public UserVm restoreVirtualMachine(final Account caller, final long vmId, final Long newTemplateId, + final Long rootDiskOfferingId, + final boolean expunge, final Map details) throws InsufficientCapacityException, ResourceUnavailableException { Long userId = caller.getId(); _userDao.findById(userId); UserVmVO vm = _vmDao.findById(vmId); @@ -7789,9 +7832,10 @@ public UserVm restoreVirtualMachine(final Account caller, final long vmId, final } } - List newVols = new ArrayList<>(); + DiskOffering diskOffering = rootDiskOfferingId != null ? _diskOfferingDao.findById(rootDiskOfferingId) : null; for (VolumeVO root : rootVols) { if ( !Volume.State.Allocated.equals(root.getState()) || newTemplateId != null ) { + _volumeService.validateDestroyVolume(root, caller, expunge, false); final UserVmVO userVm = vm; Pair vmAndNewVol = Transaction.execute(new TransactionCallbackWithException, CloudRuntimeException>() { @Override @@ -7822,15 +7866,9 @@ public Pair doInTransaction(final TransactionStatus status) th } else { newVol = volumeMgr.allocateDuplicateVolume(root, null); } - newVols.add(newVol); - if (userVmDetailsDao.findDetail(userVm.getId(), VmDetailConstants.ROOT_DISK_SIZE) == null && !newVol.getSize().equals(template.getSize())) { - VolumeVO resizedVolume = (VolumeVO) newVol; - if (template.getSize() != null) { - resizedVolume.setSize(template.getSize()); - _volsDao.update(resizedVolume.getId(), resizedVolume); - } - } + updateVolume(newVol, template, userVm, diskOffering, details); + volumeMgr.saveVolumeDetails(newVol.getDiskOfferingId(), newVol.getId()); // 1. Save usage event and update resource count for user vm volumes try { @@ -7860,7 +7898,7 @@ public Pair doInTransaction(final TransactionStatus status) th // Detach, destroy and create the usage event for the old root volume. _volsDao.detachVolume(root.getId()); - volumeMgr.destroyVolume(root); + _volumeService.destroyVolume(root.getId(), caller, expunge, false); // For VMware hypervisor since the old root volume is replaced by the new root volume, force expunge old root volume if it has been created in storage if (vm.getHypervisorType() == HypervisorType.VMware) { @@ -7923,6 +7961,48 @@ public Pair doInTransaction(final TransactionStatus status) th } + private void updateVolume(Volume vol, VMTemplateVO template, UserVmVO userVm, DiskOffering diskOffering, Map details) { + VolumeVO resizedVolume = (VolumeVO) vol; + + if (userVmDetailsDao.findDetail(userVm.getId(), VmDetailConstants.ROOT_DISK_SIZE) == null && !vol.getSize().equals(template.getSize())) { + if (template.getSize() != null) { + resizedVolume.setSize(template.getSize()); + } + } + + if (diskOffering != null) { + resizedVolume.setDiskOfferingId(diskOffering.getId()); + resizedVolume.setSize(diskOffering.getDiskSize()); + if (diskOffering.isCustomized()) { + resizedVolume.setSize(vol.getSize()); + } + if (diskOffering.getMinIops() != null) { + resizedVolume.setMinIops(diskOffering.getMinIops()); + } + if (diskOffering.getMaxIops() != null) { + resizedVolume.setMaxIops(diskOffering.getMaxIops()); + } + } + + if (MapUtils.isNotEmpty(details)) { + if (StringUtils.isNumeric(details.get(VmDetailConstants.ROOT_DISK_SIZE))) { + Long rootDiskSize = Long.parseLong(details.get(VmDetailConstants.ROOT_DISK_SIZE)) * GiB_TO_BYTES; + resizedVolume.setSize(rootDiskSize); + } + + String minIops = details.get(MIN_IOPS); + String maxIops = details.get(MAX_IOPS); + + if (StringUtils.isNumeric(minIops)) { + resizedVolume.setMinIops(Long.parseLong(minIops)); + } + if (StringUtils.isNumeric(maxIops)) { + resizedVolume.setMinIops(Long.parseLong(maxIops)); + } + } + _volsDao.update(resizedVolume.getId(), resizedVolume); + } + private void updateVMDynamicallyScalabilityUsingTemplate(UserVmVO vm, Long newTemplateId) { ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(vm.getServiceOfferingId()); VMTemplateVO newTemplate = _templateDao.findById(newTemplateId); diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 069f749e3599..e809ebb8a885 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -184,6 +184,9 @@ import java.util.Set; import java.util.stream.Collectors; +import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS; +import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS; + public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { public static final String VM_IMPORT_DEFAULT_TEMPLATE_NAME = "system-default-vm-import-dummy-template.iso"; public static final String KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME = "kvm-default-vm-import-dummy-template"; @@ -1166,12 +1169,12 @@ private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedI throw new InvalidParameterValueException(String.format("Root disk ID: %s size is invalid", rootDisk.getDiskId())); } Long minIops = null; - if (details.containsKey("minIops")) { - minIops = Long.parseLong(details.get("minIops")); + if (details.containsKey(MIN_IOPS)) { + minIops = Long.parseLong(details.get(MIN_IOPS)); } Long maxIops = null; - if (details.containsKey("maxIops")) { - maxIops = Long.parseLong(details.get("maxIops")); + if (details.containsKey(MAX_IOPS)) { + maxIops = Long.parseLong(details.get(MAX_IOPS)); } DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); diskProfileStoragePoolList.add(importDisk(rootDisk, userVm, cluster, diskOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()), diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index e2c2b8ef9e26..303a9b08b1ca 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -48,8 +48,6 @@ import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.GuestOSVO; import com.cloud.storage.ScopeType; -import com.cloud.storage.Snapshot; -import com.cloud.storage.SnapshotVO; import com.cloud.storage.Storage; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; @@ -1264,18 +1262,6 @@ public void testRestoreVMWithVolumeSnapshots() throws ResourceUnavailableExcepti when(cmd.getTemplateId()).thenReturn(2L); when(userVmDao.findById(vmId)).thenReturn(userVmVoMock); - List volumes = new ArrayList<>(); - long rootVolumeId = 1l; - VolumeVO rootVolumeOfVm = Mockito.mock(VolumeVO.class); - Mockito.when(rootVolumeOfVm.getId()).thenReturn(rootVolumeId); - volumes.add(rootVolumeOfVm); - when(volumeDaoMock.findByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(volumes); - - List snapshots = new ArrayList<>(); - SnapshotVO snapshot = Mockito.mock(SnapshotVO.class); - snapshots.add(snapshot); - when(snapshotDaoMock.listByStatus(rootVolumeId, Snapshot.State.Creating, Snapshot.State.CreatedOnPrimary, Snapshot.State.BackingUp)).thenReturn(snapshots); - userVmManagerImpl.restoreVM(cmd); } @@ -1289,7 +1275,7 @@ public void testRestoreVirtualMachineNoOwner() throws ResourceUnavailableExcepti when(userVmVoMock.getAccountId()).thenReturn(accountId); when(accountDao.findById(accountId)).thenReturn(null); - userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId); + userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null); } @Test(expected = PermissionDeniedException.class) @@ -1303,7 +1289,7 @@ public void testRestoreVirtualMachineOwnerDisabled() throws ResourceUnavailableE when(accountDao.findById(accountId)).thenReturn(callerAccount); when(callerAccount.getState()).thenReturn(Account.State.DISABLED); - userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId); + userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null); } @Test(expected = CloudRuntimeException.class) @@ -1318,7 +1304,7 @@ public void testRestoreVirtualMachineNotInRightState() throws ResourceUnavailabl when(accountDao.findById(accountId)).thenReturn(callerAccount); when(userVmVoMock.getState()).thenReturn(VirtualMachine.State.Starting); - userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId); + userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null); } @Test(expected = InvalidParameterValueException.class) @@ -1339,7 +1325,7 @@ public void testRestoreVirtualMachineNoRootVolume() throws ResourceUnavailableEx when(templateDao.findById(currentTemplateId)).thenReturn(currentTemplate); when(volumeDaoMock.findByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(new ArrayList()); - userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId); + userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null); } @Test(expected = InvalidParameterValueException.class) @@ -1366,7 +1352,7 @@ public void testRestoreVirtualMachineMoreThanOneRootVolume() throws ResourceUnav volumes.add(rootVolume2); when(volumeDaoMock.findByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(volumes); - userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId); + userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null); } @Test(expected = InvalidParameterValueException.class) @@ -1393,6 +1379,6 @@ public void testRestoreVirtualMachineWithVMSnapshots() throws ResourceUnavailabl vmSnapshots.add(vmSnapshot); when(vmSnapshotDaoMock.findByVm(vmId)).thenReturn(vmSnapshots); - userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId); + userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null); } } diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index 9390d2a7d620..db17b20ef9dc 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -164,33 +164,10 @@ export default { label: 'label.reinstall.vm', message: 'message.reinstall.vm', dataView: true, - args: ['virtualmachineid', 'templateid'], - filters: (record) => { - var filters = {} - var filterParams = {} - filterParams.hypervisortype = record.hypervisor - filterParams.zoneid = record.zoneid - filters.templateid = filterParams - return filters - }, + popup: true, show: (record) => { return ['Running', 'Stopped'].includes(record.state) }, - mapping: { - virtualmachineid: { - value: (record) => { return record.id } - } - }, disabled: (record) => { return record.hostcontrolstate === 'Offline' }, - successMethod: (obj, result) => { - const vm = result.jobresult.virtualmachine || {} - if (result.jobstatus === 1 && vm.password) { - const name = vm.displayname || vm.name || vm.id - obj.$notification.success({ - message: `${obj.$t('label.reinstall.vm')}: ` + name, - description: `${obj.$t('label.password.reset.confirm')}: ` + vm.password, - duration: 0 - }) - } - } + component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ReinstallVm.vue'))) }, { api: 'createVMSnapshot', diff --git a/ui/src/views/compute/ReinstallVm.vue b/ui/src/views/compute/ReinstallVm.vue new file mode 100644 index 000000000000..ee07011fe283 --- /dev/null +++ b/ui/src/views/compute/ReinstallVm.vue @@ -0,0 +1,307 @@ +// 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. + + + + + +