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
This commit is contained in:
Vishesh 2024-04-12 17:47:52 +05:30 committed by GitHub
parent d3e020a545
commit b998e7dbb6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 541 additions and 107 deletions

View File

@ -175,6 +175,8 @@ public interface VolumeApiService {
boolean validateVolumeSizeInBytes(long size); boolean validateVolumeSizeInBytes(long size);
void validateDestroyVolume(Volume volume, Account caller, boolean expunge, boolean forceExpunge);
Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) throws ResourceAllocationException; Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) throws ResourceAllocationException;
void publishVolumeCreationUsageEvent(Volume volume); void publishVolumeCreationUsageEvent(Volume volume);

View File

@ -492,7 +492,7 @@ public interface UserVmService {
UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException; 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, UserVm upgradeVirtualMachine(ScaleVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException,
VirtualMachineMigrationException; VirtualMachineMigrationException;

View File

@ -16,7 +16,9 @@
// under the License. // under the License.
package org.apache.cloudstack.api.command.user.vm; package org.apache.cloudstack.api.command.user.vm;
import com.cloud.vm.VmDetailConstants;
import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.acl.SecurityChecker.AccessType;
@ -42,6 +44,8 @@ import com.cloud.user.Account;
import com.cloud.uservm.UserVm; import com.cloud.uservm.UserVm;
import com.cloud.vm.VirtualMachine; 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}, @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, requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true) 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") 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; 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 @Override
public String getEventType() { public String getEventType() {
return EventTypes.EVENT_VM_RESTORE; return EventTypes.EVENT_VM_RESTORE;
@ -112,6 +138,22 @@ public class RestoreVMCmd extends BaseAsyncCmd implements UserCmd {
return getVmId(); 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 @Override
public Long getApiResourceId() { public Long getApiResourceId() {
return getId(); return getId();

View File

@ -254,7 +254,7 @@ public interface VirtualMachineManager extends Manager {
*/ */
boolean unmanage(String vmUuid); 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); boolean checkIfVmHasClusterWideVolumes(Long vmId);

View File

@ -5623,20 +5623,20 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
} }
@Override @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(); final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
VmWorkJobVO placeHolder = null; VmWorkJobVO placeHolder = null;
placeHolder = createPlaceHolderWork(vmId); placeHolder = createPlaceHolderWork(vmId);
try { try {
return orchestrateRestoreVirtualMachine(vmId, newTemplateId); return orchestrateRestoreVirtualMachine(vmId, newTemplateId, rootDiskOfferingId, expunge, details);
} finally { } finally {
if (placeHolder != null) { if (placeHolder != null) {
_workJobDao.expunge(placeHolder.getId()); _workJobDao.expunge(placeHolder.getId());
} }
} }
} else { } else {
final Outcome<VirtualMachine> outcome = restoreVirtualMachineThroughJobQueue(vmId, newTemplateId); final Outcome<VirtualMachine> outcome = restoreVirtualMachineThroughJobQueue(vmId, newTemplateId, rootDiskOfferingId, expunge, details);
retrieveVmFromJobOutcome(outcome, String.valueOf(vmId), "restoreVirtualMachine"); 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 { 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 new templateId " + newTemplateId); s_logger.debug("Restoring vm " + vmId + " with templateId : " + newTemplateId + " diskOfferingId : " + rootDiskOfferingId + " details : " + details);
final CallContext context = CallContext.current(); final CallContext context = CallContext.current();
final Account account = context.getCallingAccount(); 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(); String commandName = VmWorkRestore.class.getName();
Pair<VmWorkJobVO, Long> pendingWorkJob = retrievePendingWorkJob(vmId, commandName); 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); Pair<VmWorkJobVO, VmWork> newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId);
workJob = newVmWorkJobAndInfo.first(); workJob = newVmWorkJobAndInfo.first();
VmWorkRestore workInfo = new VmWorkRestore(newVmWorkJobAndInfo.second(), newTemplateId); VmWorkRestore workInfo = new VmWorkRestore(newVmWorkJobAndInfo.second(), newTemplateId, rootDiskOfferingId, expunge, details);
setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId);
} }
@ -5682,7 +5682,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
@ReflectionUse @ReflectionUse
private Pair<JobInfo.Status, String> orchestrateRestoreVirtualMachine(final VmWorkRestore work) throws Exception { private Pair<JobInfo.Status, String> orchestrateRestoreVirtualMachine(final VmWorkRestore work) throws Exception {
VMInstanceVO vm = findVmById(work.getVmId()); 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<>(); HashMap<Long, String> passwordMap = new HashMap<>();
passwordMap.put(uservm.getId(), uservm.getPassword()); passwordMap.put(uservm.getId(), uservm.getPassword());
return new Pair<>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(passwordMap)); return new Pair<>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(passwordMap));

View File

@ -16,23 +16,38 @@
// under the License. // under the License.
package com.cloud.vm; package com.cloud.vm;
import java.util.Map;
public class VmWorkRestore extends VmWork { public class VmWorkRestore extends VmWork {
private static final long serialVersionUID = 195901782359759635L; private static final long serialVersionUID = 195901782359759635L;
private Long templateId; private Long templateId;
private Long rootDiskOfferingId;
private Map<String,String> details;
public VmWorkRestore(long userId, long accountId, long vmId, String handlerName, Long templateId) { private boolean expunge;
super(userId, accountId, vmId, handlerName);
this.templateId = templateId; public VmWorkRestore(VmWork vmWork, Long templateId, Long rootDiskOfferingId, boolean expunge, Map<String,String> details) {
}
public VmWorkRestore(VmWork vmWork, Long templateId) {
super(vmWork); super(vmWork);
this.templateId = templateId; this.templateId = templateId;
this.rootDiskOfferingId = rootDiskOfferingId;
this.expunge = expunge;
this.details = details;
} }
public Long getTemplateId() { public Long getTemplateId() {
return templateId; return templateId;
} }
public Long getRootDiskOfferingId() {
return rootDiskOfferingId;
}
public boolean getExpunge() {
return expunge;
}
public Map<String, String> getDetails() {
return details;
}
} }

View File

@ -61,6 +61,9 @@ import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.UserVmDetailsDao;
import com.cloud.vm.dao.VMInstanceDao; 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 @Component
public class CloudOrchestrator implements OrchestrationService { public class CloudOrchestrator implements OrchestrationService {
@ -196,8 +199,8 @@ public class CloudOrchestrator implements OrchestrationService {
Map<String, String> userVmDetails = _userVmDetailsDao.listDetailsKeyPairs(vm.getId()); Map<String, String> userVmDetails = _userVmDetailsDao.listDetailsKeyPairs(vm.getId());
if (userVmDetails != null) { if (userVmDetails != null) {
String minIops = userVmDetails.get("minIops"); String minIops = userVmDetails.get(MIN_IOPS);
String maxIops = userVmDetails.get("maxIops"); String maxIops = userVmDetails.get(MAX_IOPS);
rootDiskOfferingInfo.setMinIops(minIops != null && minIops.trim().length() > 0 ? Long.parseLong(minIops) : null); rootDiskOfferingInfo.setMinIops(minIops != null && minIops.trim().length() > 0 ? Long.parseLong(minIops) : null);
rootDiskOfferingInfo.setMaxIops(maxIops != null && maxIops.trim().length() > 0 ? Long.parseLong(maxIops) : null); rootDiskOfferingInfo.setMaxIops(maxIops != null && maxIops.trim().length() > 0 ? Long.parseLong(maxIops) : null);

View File

@ -949,18 +949,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
vol = _volsDao.persist(vol); vol = _volsDao.persist(vol);
List<VolumeDetailVO> volumeDetailsVO = new ArrayList<VolumeDetailVO>(); saveVolumeDetails(offering.getId(), vol.getId());
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);
}
if (StringUtils.isNotBlank(configurationId)) { if (StringUtils.isNotBlank(configurationId)) {
VolumeDetailVO deployConfigurationDetail = new VolumeDetailVO(vol.getId(), VmDetailConstants.DEPLOY_AS_IS_CONFIGURATION, configurationId, false); 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); 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) @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating ROOT volume", create = true)
@Override @Override
public List<DiskProfile> allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm, public List<DiskProfile> allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm,

View File

@ -1724,11 +1724,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
return _volStateMachine.transitTo(vol, event, null, _volsDao); return _volStateMachine.transitTo(vol, event, null, _volsDao);
} }
@Override public void validateDestroyVolume(Volume volume, Account caller, boolean expunge, boolean forceExpunge) {
@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);
if (expunge) { if (expunge) {
// When trying to expunge, permission is denied when the caller is not an admin and the AllowUserExpungeRecoverVolume is false for the caller. // 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(); 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) { } 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"); 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); destroyVolumeIfPossible(volume);

View File

@ -18,6 +18,8 @@ package com.cloud.vm;
import static com.cloud.configuration.ConfigurationManagerImpl.VM_USERDATA_MAX_LENGTH; import static com.cloud.configuration.ConfigurationManagerImpl.VM_USERDATA_MAX_LENGTH;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize; 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.IOException;
import java.io.StringReader; import java.io.StringReader;
@ -566,6 +568,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
@Inject @Inject
private VmStatsDao vmStatsDao; private VmStatsDao vmStatsDao;
@Inject @Inject
private DataCenterDao dataCenterDao;
@Inject
private MessageBus messageBus; private MessageBus messageBus;
@Inject @Inject
protected CommandSetupHelper commandSetupHelper; protected CommandSetupHelper commandSetupHelper;
@ -2148,11 +2152,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
Long maxIopsInNewDiskOffering = null; Long maxIopsInNewDiskOffering = null;
boolean autoMigrate = false; boolean autoMigrate = false;
boolean shrinkOk = false; boolean shrinkOk = false;
if (customParameters.containsKey(ApiConstants.MIN_IOPS)) { if (customParameters.containsKey(MIN_IOPS)) {
minIopsInNewDiskOffering = Long.parseLong(customParameters.get(ApiConstants.MIN_IOPS)); minIopsInNewDiskOffering = Long.parseLong(customParameters.get(MIN_IOPS));
} }
if (customParameters.containsKey(ApiConstants.MAX_IOPS)) { if (customParameters.containsKey(MAX_IOPS)) {
minIopsInNewDiskOffering = Long.parseLong(customParameters.get(ApiConstants.MAX_IOPS)); minIopsInNewDiskOffering = Long.parseLong(customParameters.get(MAX_IOPS));
} }
if (customParameters.containsKey(ApiConstants.AUTO_MIGRATE)) { if (customParameters.containsKey(ApiConstants.AUTO_MIGRATE)) {
autoMigrate = Boolean.parseBoolean(customParameters.get(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); ServiceOfferingVO offering = serviceOfferingDao.findById(vmInstance.getId(), serviceOfferingId);
if (offering != null && offering.getRemoved() == null) { if (offering != null && offering.getRemoved() == null) {
if (offering.isVolatileVm()) { if (offering.isVolatileVm()) {
return restoreVMInternal(caller, vmInstance, null); return restoreVMInternal(caller, vmInstance);
} }
} else { } else {
throw new InvalidParameterValueException("Unable to find service offering: " + serviceOfferingId + " corresponding to the vm"); 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 // if specified, minIops should be <= maxIops
private void verifyDetails(Map<String,String> details) { private void verifyDetails(Map<String,String> details) {
if (details != null) { if (details != null) {
String minIops = details.get("minIops"); String minIops = details.get(MIN_IOPS);
String maxIops = details.get("maxIops"); String maxIops = details.get(MAX_IOPS);
verifyMinAndMaxIops(minIops, maxIops); verifyMinAndMaxIops(minIops, maxIops);
@ -7660,6 +7664,20 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
return false; 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 @Override
public UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException { public UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException {
// Input validation // Input validation
@ -7667,6 +7685,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
long vmId = cmd.getVmId(); long vmId = cmd.getVmId();
Long newTemplateId = cmd.getTemplateId(); 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); UserVmVO vm = _vmDao.findById(vmId);
if (vm == null) { if (vm == null) {
@ -7674,20 +7697,38 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
ex.addProxyObject(String.valueOf(vmId), "vmId"); ex.addProxyObject(String.valueOf(vmId), "vmId");
throw ex; throw ex;
} }
_accountMgr.checkAccess(caller, null, true, vm); _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 //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); 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)) { 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."); 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); 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 { 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); 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) { 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 @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(); Long userId = caller.getId();
_userDao.findById(userId); _userDao.findById(userId);
UserVmVO vm = _vmDao.findById(vmId); 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) { for (VolumeVO root : rootVols) {
if ( !Volume.State.Allocated.equals(root.getState()) || newTemplateId != null ) { if ( !Volume.State.Allocated.equals(root.getState()) || newTemplateId != null ) {
_volumeService.validateDestroyVolume(root, caller, expunge, false);
final UserVmVO userVm = vm; final UserVmVO userVm = vm;
Pair<UserVmVO, Volume> vmAndNewVol = Transaction.execute(new TransactionCallbackWithException<Pair<UserVmVO, Volume>, CloudRuntimeException>() { Pair<UserVmVO, Volume> vmAndNewVol = Transaction.execute(new TransactionCallbackWithException<Pair<UserVmVO, Volume>, CloudRuntimeException>() {
@Override @Override
@ -7822,15 +7866,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
} else { } else {
newVol = volumeMgr.allocateDuplicateVolume(root, null); newVol = volumeMgr.allocateDuplicateVolume(root, null);
} }
newVols.add(newVol);
if (userVmDetailsDao.findDetail(userVm.getId(), VmDetailConstants.ROOT_DISK_SIZE) == null && !newVol.getSize().equals(template.getSize())) { updateVolume(newVol, template, userVm, diskOffering, details);
VolumeVO resizedVolume = (VolumeVO) newVol; volumeMgr.saveVolumeDetails(newVol.getDiskOfferingId(), newVol.getId());
if (template.getSize() != null) {
resizedVolume.setSize(template.getSize());
_volsDao.update(resizedVolume.getId(), resizedVolume);
}
}
// 1. Save usage event and update resource count for user vm volumes // 1. Save usage event and update resource count for user vm volumes
try { 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. // Detach, destroy and create the usage event for the old root volume.
_volsDao.detachVolume(root.getId()); _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 // 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) { 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) { private void updateVMDynamicallyScalabilityUsingTemplate(UserVmVO vm, Long newTemplateId) {
ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(vm.getServiceOfferingId()); ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(vm.getServiceOfferingId());
VMTemplateVO newTemplate = _templateDao.findById(newTemplateId); VMTemplateVO newTemplate = _templateDao.findById(newTemplateId);

View File

@ -184,6 +184,9 @@ import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; 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 class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
public static final String VM_IMPORT_DEFAULT_TEMPLATE_NAME = "system-default-vm-import-dummy-template.iso"; 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"; 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())); throw new InvalidParameterValueException(String.format("Root disk ID: %s size is invalid", rootDisk.getDiskId()));
} }
Long minIops = null; Long minIops = null;
if (details.containsKey("minIops")) { if (details.containsKey(MIN_IOPS)) {
minIops = Long.parseLong(details.get("minIops")); minIops = Long.parseLong(details.get(MIN_IOPS));
} }
Long maxIops = null; Long maxIops = null;
if (details.containsKey("maxIops")) { if (details.containsKey(MAX_IOPS)) {
maxIops = Long.parseLong(details.get("maxIops")); maxIops = Long.parseLong(details.get(MAX_IOPS));
} }
DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId());
diskProfileStoragePoolList.add(importDisk(rootDisk, userVm, cluster, diskOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()), diskProfileStoragePoolList.add(importDisk(rootDisk, userVm, cluster, diskOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()),

View File

@ -48,8 +48,6 @@ import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.GuestOSVO; import com.cloud.storage.GuestOSVO;
import com.cloud.storage.ScopeType; import com.cloud.storage.ScopeType;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Storage; import com.cloud.storage.Storage;
import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.Volume; import com.cloud.storage.Volume;
@ -1264,18 +1262,6 @@ public class UserVmManagerImplTest {
when(cmd.getTemplateId()).thenReturn(2L); when(cmd.getTemplateId()).thenReturn(2L);
when(userVmDao.findById(vmId)).thenReturn(userVmVoMock); 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); userVmManagerImpl.restoreVM(cmd);
} }
@ -1289,7 +1275,7 @@ public class UserVmManagerImplTest {
when(userVmVoMock.getAccountId()).thenReturn(accountId); when(userVmVoMock.getAccountId()).thenReturn(accountId);
when(accountDao.findById(accountId)).thenReturn(null); when(accountDao.findById(accountId)).thenReturn(null);
userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId); userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null);
} }
@Test(expected = PermissionDeniedException.class) @Test(expected = PermissionDeniedException.class)
@ -1303,7 +1289,7 @@ public class UserVmManagerImplTest {
when(accountDao.findById(accountId)).thenReturn(callerAccount); when(accountDao.findById(accountId)).thenReturn(callerAccount);
when(callerAccount.getState()).thenReturn(Account.State.DISABLED); when(callerAccount.getState()).thenReturn(Account.State.DISABLED);
userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId); userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null);
} }
@Test(expected = CloudRuntimeException.class) @Test(expected = CloudRuntimeException.class)
@ -1318,7 +1304,7 @@ public class UserVmManagerImplTest {
when(accountDao.findById(accountId)).thenReturn(callerAccount); when(accountDao.findById(accountId)).thenReturn(callerAccount);
when(userVmVoMock.getState()).thenReturn(VirtualMachine.State.Starting); when(userVmVoMock.getState()).thenReturn(VirtualMachine.State.Starting);
userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId); userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null);
} }
@Test(expected = InvalidParameterValueException.class) @Test(expected = InvalidParameterValueException.class)
@ -1339,7 +1325,7 @@ public class UserVmManagerImplTest {
when(templateDao.findById(currentTemplateId)).thenReturn(currentTemplate); when(templateDao.findById(currentTemplateId)).thenReturn(currentTemplate);
when(volumeDaoMock.findByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(new ArrayList<VolumeVO>()); 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) @Test(expected = InvalidParameterValueException.class)
@ -1366,7 +1352,7 @@ public class UserVmManagerImplTest {
volumes.add(rootVolume2); volumes.add(rootVolume2);
when(volumeDaoMock.findByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(volumes); 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) @Test(expected = InvalidParameterValueException.class)
@ -1393,6 +1379,6 @@ public class UserVmManagerImplTest {
vmSnapshots.add(vmSnapshot); vmSnapshots.add(vmSnapshot);
when(vmSnapshotDaoMock.findByVm(vmId)).thenReturn(vmSnapshots); when(vmSnapshotDaoMock.findByVm(vmId)).thenReturn(vmSnapshots);
userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId); userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null);
} }
} }

View File

@ -164,33 +164,10 @@ export default {
label: 'label.reinstall.vm', label: 'label.reinstall.vm',
message: 'message.reinstall.vm', message: 'message.reinstall.vm',
dataView: true, dataView: true,
args: ['virtualmachineid', 'templateid'], popup: true,
filters: (record) => {
var filters = {}
var filterParams = {}
filterParams.hypervisortype = record.hypervisor
filterParams.zoneid = record.zoneid
filters.templateid = filterParams
return filters
},
show: (record) => { return ['Running', 'Stopped'].includes(record.state) }, show: (record) => { return ['Running', 'Stopped'].includes(record.state) },
mapping: {
virtualmachineid: {
value: (record) => { return record.id }
}
},
disabled: (record) => { return record.hostcontrolstate === 'Offline' }, disabled: (record) => { return record.hostcontrolstate === 'Offline' },
successMethod: (obj, result) => { component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ReinstallVm.vue')))
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
})
}
}
}, },
{ {
api: 'createVMSnapshot', api: 'createVMSnapshot',

View File

@ -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>