Skip to content

Commit

Permalink
Allow overriding root disk offering & size, and expunge old root disk…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
vishesh92 committed Apr 12, 2024
1 parent d3e020a commit b998e7d
Show file tree
Hide file tree
Showing 14 changed files with 541 additions and 107 deletions.
2 changes: 2 additions & 0 deletions api/src/main/java/com/cloud/storage/VolumeApiService.java
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account acc

boolean validateVolumeSizeInBytes(long size);

void validateDestroyVolume(Volume volume, Account caller, boolean expunge, boolean forceExpunge);

Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) throws ResourceAllocationException;

void publishVolumeCreationUsageEvent(Volume volume);
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/java/com/cloud/vm/UserVmService.java
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ UserVm moveVMToUser(AssignVMCmd moveUserVMCmd) throws ResourceAllocationExceptio

UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException;

UserVm restoreVirtualMachine(Account caller, long vmId, Long newTemplateId) throws InsufficientCapacityException, ResourceUnavailableException;
UserVm restoreVirtualMachine(Account caller, long vmId, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map<String, String> details) throws InsufficientCapacityException, ResourceUnavailableException;

UserVm upgradeVirtualMachine(ScaleVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException,
VirtualMachineMigrationException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -42,6 +44,8 @@
import com.cloud.uservm.UserVm;
import com.cloud.vm.VirtualMachine;

import java.util.Map;

@APICommand(name = "restoreVirtualMachine", description = "Restore a VM to original template/ISO or new template/ISO", responseObject = UserVmResponse.class, since = "3.0.0", responseView = ResponseView.Restricted, entityType = {VirtualMachine.class},
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true)
Expand All @@ -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;
Expand Down Expand Up @@ -112,6 +138,22 @@ public Long getId() {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ static String getHypervisorHostname(String name) {
*/
boolean unmanage(String vmUuid);

UserVm restoreVirtualMachine(long vmId, Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException;
UserVm restoreVirtualMachine(long vmId, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map<String, String> details) throws ResourceUnavailableException, InsufficientCapacityException;

boolean checkIfVmHasClusterWideVolumes(Long vmId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5623,20 +5623,20 @@ protected void resourceCountDecrement (long accountId, Long cpu, Long memory) {
}

@Override
public UserVm restoreVirtualMachine(final long vmId, final Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException {
public UserVm restoreVirtualMachine(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, final Map<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");

Expand All @@ -5653,14 +5653,14 @@ public UserVm restoreVirtualMachine(final long vmId, final Long newTemplateId) t
}
}

private UserVm orchestrateRestoreVirtualMachine(final long vmId, final Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException {
s_logger.debug("Restoring vm " + vmId + " with new templateId " + newTemplateId);
private UserVm orchestrateRestoreVirtualMachine(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, final Map<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);

Expand All @@ -5670,7 +5670,7 @@ public Outcome<VirtualMachine> restoreVirtualMachineThroughJobQueue(final long v
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);
}
Expand All @@ -5682,7 +5682,7 @@ public Outcome<VirtualMachine> restoreVirtualMachineThroughJobQueue(final long v
@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));
Expand Down
27 changes: 21 additions & 6 deletions engine/orchestration/src/main/java/com/cloud/vm/VmWorkRestore.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
import com.cloud.vm.dao.UserVmDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;

import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS;
import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS;

@Component
public class CloudOrchestrator implements OrchestrationService {

Expand Down Expand Up @@ -196,8 +199,8 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -949,18 +949,7 @@ private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering

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);
Expand All @@ -985,6 +974,32 @@ private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering
return toDiskProfile(vol, offering);
}

@Override
public void saveVolumeDetails(Long diskOfferingId, Long volumeId) {
List<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,
Expand Down
14 changes: 9 additions & 5 deletions server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -1724,11 +1724,7 @@ public boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitio
return _volStateMachine.transitTo(vol, event, null, _volsDao);
}

@Override
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_DESTROY, eventDescription = "destroying a volume")
public Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge) {
VolumeVO volume = retrieveAndValidateVolume(volumeId, caller);

public void validateDestroyVolume(Volume volume, Account caller, boolean expunge, boolean forceExpunge) {
if (expunge) {
// When trying to expunge, permission is denied when the caller is not an admin and the AllowUserExpungeRecoverVolume is false for the caller.
final Long userId = caller.getAccountId();
Expand All @@ -1738,6 +1734,14 @@ public Volume destroyVolume(long volumeId, Account caller, boolean expunge, bool
} else if (volume.getState() == Volume.State.Allocated || volume.getState() == Volume.State.Uploaded) {
throw new InvalidParameterValueException("The volume in Allocated/Uploaded state can only be expunged not destroyed/recovered");
}
}

@Override
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_DESTROY, eventDescription = "destroying a volume")
public Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge) {
VolumeVO volume = retrieveAndValidateVolume(volumeId, caller);

validateDestroyVolume(volume, caller, expunge, forceExpunge);

destroyVolumeIfPossible(volume);

Expand Down

0 comments on commit b998e7d

Please sign in to comment.