This is an automated email from the ASF dual-hosted git repository. rohit pushed a commit to branch 4.19 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.19 by this push: new b998e7dbb63 Allow overriding root disk offering & size, and expunge old root disk while restoring a VM (#8800) b998e7dbb63 is described below commit b998e7dbb6355c4165b00a2792062f2d446713dd Author: Vishesh <vishes...@gmail.com> AuthorDate: Fri Apr 12 17:47:52 2024 +0530 Allow overriding root disk offering & size, and expunge old root disk while restoring a VM (#8800) * Allow overriding root diskoffering id & size while restoring VM * UI changes * Allow expunging of old disk while restoring a VM * Resolve comments * Address comments * Duplicate volume's details while duplicating volume * Allow setting IOPS for the new volume * minor cleanup * fixup * Add checks for template size * Replace strings for IOPS with constants * Fix saveVolumeDetails method * Fixup * Fixup UI styling --- .../java/com/cloud/storage/VolumeApiService.java | 2 + api/src/main/java/com/cloud/vm/UserVmService.java | 2 +- .../api/command/user/vm/RestoreVMCmd.java | 42 +++ .../java/com/cloud/vm/VirtualMachineManager.java | 2 +- .../com/cloud/vm/VirtualMachineManagerImpl.java | 18 +- .../src/main/java/com/cloud/vm/VmWorkRestore.java | 27 +- .../engine/orchestration/CloudOrchestrator.java | 7 +- .../engine/orchestration/VolumeOrchestrator.java | 39 ++- .../com/cloud/storage/VolumeApiServiceImpl.java | 14 +- .../main/java/com/cloud/vm/UserVmManagerImpl.java | 124 +++++++-- .../cloudstack/vm/UnmanagedVMsManagerImpl.java | 11 +- .../java/com/cloud/vm/UserVmManagerImplTest.java | 26 +- ui/src/config/section/compute.js | 27 +- ui/src/views/compute/ReinstallVm.vue | 307 +++++++++++++++++++++ 14 files changed, 541 insertions(+), 107 deletions(-) diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index a673df12d0f..4f09702b7db 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 @@ public interface VolumeApiService { 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 c32c099ed3a..787ed7bde37 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 @@ public interface UserVmService { 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<String, String> 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 4b59bf560cb..17c4e97eb3b 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.user.Account; 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 class RestoreVMCmd extends BaseAsyncCmd implements UserCmd { return getVmId(); } + public Long getRootDiskOfferingId() { + return rootDiskOfferingId; + } + + public Map<String, String> getDetails() { + Map<String, String> 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 8cd67f25331..3f7d6be6d88 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 @@ public interface VirtualMachineManager extends Manager { */ boolean unmanage(String vmUuid); - UserVm restoreVirtualMachine(long vmId, Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException; + UserVm restoreVirtualMachine(long vmId, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map<String, String> 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 59d129bc065..243613907ff 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 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } @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<String, String> 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<VirtualMachine> outcome = restoreVirtualMachineThroughJobQueue(vmId, newTemplateId); + final Outcome<VirtualMachine> outcome = restoreVirtualMachineThroughJobQueue(vmId, newTemplateId, rootDiskOfferingId, expunge, details); retrieveVmFromJobOutcome(outcome, String.valueOf(vmId), "restoreVirtualMachine"); @@ -5653,14 +5653,14 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } } - 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<String, String> 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<VirtualMachine> restoreVirtualMachineThroughJobQueue(final long vmId, final Long newTemplateId) { + public Outcome<VirtualMachine> restoreVirtualMachineThroughJobQueue(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, Map<String, String> details) { String commandName = VmWorkRestore.class.getName(); Pair<VmWorkJobVO, Long> pendingWorkJob = retrievePendingWorkJob(vmId, commandName); @@ -5670,7 +5670,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac Pair<VmWorkJobVO, VmWork> 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 class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @ReflectionUse private Pair<JobInfo.Status, String> 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<Long, String> 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 cb3adae27aa..ab5425a2500 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<String,String> 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<String,String> 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<String, String> 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 d639b4513e4..6763a13aed6 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.UserVmDao; 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 class CloudOrchestrator implements OrchestrationService { Map<String, String> 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 3a5b342b6e8..5c79fb64d8d 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 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati vol = _volsDao.persist(vol); - List<VolumeDetailVO> volumeDetailsVO = new ArrayList<VolumeDetailVO>(); - 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 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati return toDiskProfile(vol, offering); } + @Override + public void saveVolumeDetails(Long diskOfferingId, Long volumeId) { + List<VolumeDetailVO> 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<DiskProfile> 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 e5a33a22859..8679221107d 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 class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic 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 class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } 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 ae0d66ee482..566fcb38fc9 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 @@ package com.cloud.vm; 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 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir 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 class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir 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 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // if specified, minIops should be <= maxIops private void verifyDetails(Map<String,String> 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 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir 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 class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir long vmId = cmd.getVmId(); Long newTemplateId = cmd.getTemplateId(); + Long rootDiskOfferingId = cmd.getRootDiskOfferingId(); + boolean expunge = cmd.getExpungeRootDisk(); + Map<String, String> details = cmd.getDetails(); + + verifyDetails(details); UserVmVO vm = _vmDao.findById(vmId); if (vm == null) { @@ -7674,20 +7697,38 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir 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<String, String> 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<VolumeVO> rootVols, UserVmVO vm) { @@ -7732,7 +7773,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } @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<String, String> details) throws InsufficientCapacityException, ResourceUnavailableException { Long userId = caller.getId(); _userDao.findById(userId); UserVmVO vm = _vmDao.findById(vmId); @@ -7789,9 +7832,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } } - List<Volume> 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<UserVmVO, Volume> vmAndNewVol = Transaction.execute(new TransactionCallbackWithException<Pair<UserVmVO, Volume>, CloudRuntimeException>() { @Override @@ -7822,15 +7866,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } 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 class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // 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 class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } + private void updateVolume(Volume vol, VMTemplateVO template, UserVmVO userVm, DiskOffering diskOffering, Map<String, String> 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 069f749e359..e809ebb8a88 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.Random; 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 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { 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 e2c2b8ef9e2..303a9b08b1c 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.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.GuestOSVO; import com.cloud.storage.ScopeType; -import com.cloud.storage.Snapshot; -import com.cloud.storage.SnapshotVO; import com.cloud.storage.Storage; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; @@ -1264,18 +1262,6 @@ public class UserVmManagerImplTest { when(cmd.getTemplateId()).thenReturn(2L); when(userVmDao.findById(vmId)).thenReturn(userVmVoMock); - List<VolumeVO> volumes = new ArrayList<>(); - long rootVolumeId = 1l; - VolumeVO rootVolumeOfVm = Mockito.mock(VolumeVO.class); - Mockito.when(rootVolumeOfVm.getId()).thenReturn(rootVolumeId); - volumes.add(rootVolumeOfVm); - when(volumeDaoMock.findByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(volumes); - - List<SnapshotVO> snapshots = new ArrayList<>(); - SnapshotVO snapshot = Mockito.mock(SnapshotVO.class); - snapshots.add(snapshot); - when(snapshotDaoMock.listByStatus(rootVolumeId, Snapshot.State.Creating, Snapshot.State.CreatedOnPrimary, Snapshot.State.BackingUp)).thenReturn(snapshots); - userVmManagerImpl.restoreVM(cmd); } @@ -1289,7 +1275,7 @@ public class UserVmManagerImplTest { 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 class UserVmManagerImplTest { 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 class UserVmManagerImplTest { 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 class UserVmManagerImplTest { when(templateDao.findById(currentTemplateId)).thenReturn(currentTemplate); when(volumeDaoMock.findByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(new ArrayList<VolumeVO>()); - userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId); + userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null); } @Test(expected = InvalidParameterValueException.class) @@ -1366,7 +1352,7 @@ public class UserVmManagerImplTest { 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 class UserVmManagerImplTest { 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 9390d2a7d62..db17b20ef9d 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 00000000000..ee07011fe28 --- /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. + +<template> + <a-form + v-ctrl-enter="handleSubmit" + @finish="handleSubmit" + layout="vertical" + > + <a-alert + type="warning" + show-icon + > + <template #message><span + style="margin-bottom: 5px" + v-html="$t('message.reinstall.vm')" + /></template> + </a-alert> + <a-form-item> + <template-iso-selection + input-decorator="templateid" + :items="templates" + :selected="tabKey" + :loading="loading.templates" + :preFillContent="resource.templateid" + :key="templateKey" + @handle-search-filter="($event) => fetchAllTemplates($event)" + @update-template-iso="updateFieldValue" + /> + </a-form-item> + <a-form-item> + <template #label> + <tooltip-label + :title="$t('label.override.root.diskoffering')" + :tooltip="apiParams.diskofferingid.description" + /> + </template> + <a-switch + v-model:checked="overrideDiskOffering" + @change="val => { overrideDiskOffering = val }" + /> + </a-form-item> + <a-form-item v-if="overrideDiskOffering"> + <disk-offering-selection + :items="diskOfferings" + :row-count="diskOfferingCount" + :zoneId="resource.zoneId" + :value="diskOffering ? diskOffering.id : ''" + :loading="loading.diskOfferings" + :preFillContent="resource.diskofferingid" + :isIsoSelected="false" + :isRootDiskOffering="true" + @on-selected-disk-size="onSelectDiskSize" + @handle-search-filter="($event) => fetchDiskOfferings($event)" + /> + </a-form-item> + <a-form-item v-if="diskOffering && (diskOffering.iscustomized || diskOffering.iscustomizediops)"> + <disk-size-selection + input-decorator="rootdisksize" + :diskSelected="diskOffering" + :isCustomized="diskOffering.iscustomized" + @handler-error="handlerError" + @update-disk-size="updateFieldValue" + @update-root-disk-iops-value="updateFieldValue" + /> + </a-form-item> + <a-form-item v-if="!(diskOffering && diskOffering.iscustomized)"> + <template #label> + <tooltip-label + :title="$t('label.override.rootdisk.size')" + :tooltip="apiParams.rootdisksize.description" + /> + </template> + <a-switch + v-model:checked="overrideDiskSize" + @change="val => { overrideDiskSize = val }" + /> + <disk-size-selection + v-if="overrideDiskSize" + input-decorator="rootdisksize" + :isCustomized="true" + @update-disk-size="(input, value) => updateFieldValue('overrideRootDiskSize', value)" + style="margin-top: 10px;" + /> + </a-form-item> + <a-form-item> + <template #label> + <tooltip-label + :title="$t('label.expunge')" + :tooltip="apiParams.expunge.description" + /> + </template> + <a-switch + v-model:checked="expungeDisk" + @change="val => { expungeDisk = val }" + /> + </a-form-item> + <div + :span="24" + class="action-button" + > + <a-button @click="closeAction">{{ $t('label.cancel') }}</a-button> + <a-button + ref="submit" + type="primary" + @click="handleSubmit" + >{{ $t('label.ok') }}</a-button> + </div> + </a-form> +</template> + +<script> +import { api } from '@/api' +import DiskOfferingSelection from '@views/compute/wizard/DiskOfferingSelection' +import DiskSizeSelection from '@views/compute/wizard/DiskSizeSelection' +import TemplateIsoSelection from '@views/compute/wizard/TemplateIsoSelection' +import TooltipLabel from '@/components/widgets/TooltipLabel' +import _ from 'lodash' + +export default { + name: 'ReinstallVM', + components: { + DiskOfferingSelection, + DiskSizeSelection, + TemplateIsoSelection, + TooltipLabel + }, + props: { + resource: { + type: Object, + required: true + } + }, + inject: ['parentFetchData'], + data () { + return { + overrideDiskOffering: false, + overrideDiskSize: false, + expungeDisk: false, + selectedDiskOffering: {}, + loading: { + templates: false, + diskOfferings: false + }, + rootDiskSizeKey: 'details[0].rootdisksize', + minIopsKey: 'details[0].minIops', + maxIopsKey: 'details[0].maxIops', + rootdisksize: 0, + minIops: 0, + maxIops: 0, + templateFilter: [ + 'featured', + 'community', + 'selfexecutable', + 'sharedexecutable' + ], + diskOffering: {}, + diskOfferingCount: 0, + templateKey: 0 + } + }, + beforeCreate () { + this.apiParams = this.$getApiParams('restoreVirtualMachine') + }, + created () { + this.fetchData() + }, + methods: { + fetchData () { + this.fetchDiskOfferings({}) + this.fetchAllTemplates() + }, + closeAction () { + this.$emit('close-action') + }, + handlerError (error) { + this.error = error + }, + handleSubmit () { + const params = { + virtualmachineid: this.resource.id, + templateid: this.templateid + } + if (this.overrideDiskOffering) { + params.diskofferingid = this.diskOffering.id + if (this.diskOffering.iscustomized) { + params[this.rootDiskSizeKey] = this.rootdisksize + } + if (this.diskOffering.iscustomizediops) { + params[this.minIopsKey] = this.minIops + params[this.maxIopsKey] = this.maxIops + } + } + if (this.overrideDiskSize && this.overrideRootDiskSize) { + params.rootdisksize = this.overrideRootDiskSize + } + params.expunge = this.expungeDisk + api('restoreVirtualMachine', params).then(response => { + this.$pollJob({ + jobId: response.restorevmresponse.jobid, + successMessage: this.$t('label.reinstall.vm') + ' ' + this.$t('label.success'), + successMethod: (result) => { + const vm = result.jobresult.virtualmachine || {} + const name = vm.displayname || vm.name || vm.id + if (result.jobstatus === 1 && vm.password) { + this.$notification.success({ + message: `${this.$t('label.reinstall.vm')}: ` + name, + description: `${this.$t('label.password.reset.confirm')}: ` + vm.password, + duration: 0 + }) + } + }, + errorMessage: this.$t('label.reinstall.vm') + ' ' + this.$t('label.failed'), + errorMethod: (result) => { + this.closeAction() + }, + loadingMessage: this.$t('label.reinstall.vm') + ': ' + this.resource.name, + catchMessage: this.$t('error.fetching.async.job.result') + }) + }).catch(error => { + this.$notifyError(error) + this.closeAction() + }).finally(() => { + this.closeAction() + }) + }, + fetchAllTemplates (params) { + const promises = [] + const templates = {} + this.loading.templates = true + this.templateFilter.forEach((filter) => { + templates[filter] = { count: 0, template: [] } + promises.push(this.fetchTemplates(filter, params)) + }) + this.templates = templates + Promise.all(promises).then((response) => { + response.forEach((resItem, idx) => { + templates[this.templateFilter[idx]] = _.isEmpty(resItem.listtemplatesresponse) ? { count: 0, template: [] } : resItem.listtemplatesresponse + this.templates = { ...templates } + }) + }).catch((reason) => { + console.log(reason) + }).finally(() => { + this.loading.templates = false + }) + }, + fetchTemplates (templateFilter, params) { + const args = Object.assign({}, params) + if (args.keyword || args.category !== templateFilter) { + args.page = 1 + args.pageSize = args.pageSize || 10 + } + args.zoneid = _.get(this.zone, 'id') + args.templatefilter = templateFilter + args.details = 'all' + args.showicon = 'true' + + return new Promise((resolve, reject) => { + api('listTemplates', args).then((response) => { + resolve(response) + }).catch((reason) => { + reject(reason) + }) + }) + }, + fetchDiskOfferings (params) { + api('listDiskOfferings', { zoneid: this.resource.zoneid, listall: true, ...params }).then((response) => { + this.diskOfferings = response?.listdiskofferingsresponse?.diskoffering || [] + this.diskOfferingCount = response?.listdiskofferingsresponse?.count || 0 + }) + }, + onSelectDiskSize (rowSelected) { + this.diskOffering = rowSelected + }, + updateFieldValue (input, value) { + this[input] = value + } + } +} +</script> + +<style + scoped + lang="scss" +> +.ant-form { + width: 90vw; + + @media (min-width: 700px) { + width: 50vw; + } +} +</style>