Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow overriding root disk offering & size, and expunge old root disk while restoring a VM #8800

Merged
merged 14 commits into from
Apr 12, 2024
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 @@ -130,6 +130,8 @@ DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Lon

boolean canVmRestartOnAnotherServer(long vmId);

void saveVolumeDetails(Long diskOfferingId, Long volumeId);

/**
* Allocate a volume or multiple volumes in case of template is registered with the 'deploy-as-is' option, allowing multiple disks
*/
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 @@ -934,18 +934,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 @@ -970,6 +959,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 @@ -1711,11 +1711,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 @@ -1725,6 +1721,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
Loading
Loading