Skip to content

Commit b998e7d

Browse files
authored
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
1 parent d3e020a commit b998e7d

14 files changed

Lines changed: 541 additions & 107 deletions

File tree

api/src/main/java/com/cloud/storage/VolumeApiService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account acc
175175

176176
boolean validateVolumeSizeInBytes(long size);
177177

178+
void validateDestroyVolume(Volume volume, Account caller, boolean expunge, boolean forceExpunge);
179+
178180
Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) throws ResourceAllocationException;
179181

180182
void publishVolumeCreationUsageEvent(Volume volume);

api/src/main/java/com/cloud/vm/UserVmService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ UserVm moveVMToUser(AssignVMCmd moveUserVMCmd) throws ResourceAllocationExceptio
492492

493493
UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException;
494494

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

497497
UserVm upgradeVirtualMachine(ScaleVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException,
498498
VirtualMachineMigrationException;

api/src/main/java/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.command.user.vm;
1818

19+
import com.cloud.vm.VmDetailConstants;
1920
import org.apache.cloudstack.api.ApiCommandResourceType;
21+
import org.apache.cloudstack.api.response.DiskOfferingResponse;
2022
import org.apache.log4j.Logger;
2123

2224
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
@@ -42,6 +44,8 @@
4244
import com.cloud.uservm.UserVm;
4345
import com.cloud.vm.VirtualMachine;
4446

47+
import java.util.Map;
48+
4549
@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},
4650
requestHasSensitiveInfo = false,
4751
responseHasSensitiveInfo = true)
@@ -60,6 +64,28 @@ public class RestoreVMCmd extends BaseAsyncCmd implements UserCmd {
6064
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")
6165
private Long templateId;
6266

67+
@Parameter(name = ApiConstants.DISK_OFFERING_ID,
68+
type = CommandType.UUID,
69+
entityType = DiskOfferingResponse.class,
70+
description = "Override root volume's diskoffering.", since = "4.19.1")
71+
private Long rootDiskOfferingId;
72+
73+
@Parameter(name = ApiConstants.ROOT_DISK_SIZE,
74+
type = CommandType.LONG,
75+
description = "Override root volume's size (in GB). Analogous to details[0].rootdisksize, which takes precedence over this parameter if both are provided",
76+
since = "4.19.1")
77+
private Long rootDiskSize;
78+
79+
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.19.1",
80+
description = "used to specify the custom parameters")
81+
private Map details;
82+
83+
@Parameter(name = ApiConstants.EXPUNGE,
84+
type = CommandType.BOOLEAN,
85+
description = "Optional field to expunge old root volume after restore.",
86+
since = "4.19.1")
87+
private Boolean expungeRootDisk;
88+
6389
@Override
6490
public String getEventType() {
6591
return EventTypes.EVENT_VM_RESTORE;
@@ -112,6 +138,22 @@ public Long getId() {
112138
return getVmId();
113139
}
114140

141+
public Long getRootDiskOfferingId() {
142+
return rootDiskOfferingId;
143+
}
144+
145+
public Map<String, String> getDetails() {
146+
Map<String, String> customparameterMap = convertDetailsToMap(details);
147+
if (rootDiskSize != null && !customparameterMap.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) {
148+
customparameterMap.put(VmDetailConstants.ROOT_DISK_SIZE, rootDiskSize.toString());
149+
}
150+
return customparameterMap;
151+
}
152+
153+
public Boolean getExpungeRootDisk() {
154+
return expungeRootDisk != null && expungeRootDisk;
155+
}
156+
115157
@Override
116158
public Long getApiResourceId() {
117159
return getId();

engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ static String getHypervisorHostname(String name) {
254254
*/
255255
boolean unmanage(String vmUuid);
256256

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

259259
boolean checkIfVmHasClusterWideVolumes(Long vmId);
260260

engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5623,20 +5623,20 @@ protected void resourceCountDecrement (long accountId, Long cpu, Long memory) {
56235623
}
56245624

56255625
@Override
5626-
public UserVm restoreVirtualMachine(final long vmId, final Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException {
5626+
public UserVm restoreVirtualMachine(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, final Map<String, String> details) throws ResourceUnavailableException, InsufficientCapacityException {
56275627
final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
56285628
if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
56295629
VmWorkJobVO placeHolder = null;
56305630
placeHolder = createPlaceHolderWork(vmId);
56315631
try {
5632-
return orchestrateRestoreVirtualMachine(vmId, newTemplateId);
5632+
return orchestrateRestoreVirtualMachine(vmId, newTemplateId, rootDiskOfferingId, expunge, details);
56335633
} finally {
56345634
if (placeHolder != null) {
56355635
_workJobDao.expunge(placeHolder.getId());
56365636
}
56375637
}
56385638
} else {
5639-
final Outcome<VirtualMachine> outcome = restoreVirtualMachineThroughJobQueue(vmId, newTemplateId);
5639+
final Outcome<VirtualMachine> outcome = restoreVirtualMachineThroughJobQueue(vmId, newTemplateId, rootDiskOfferingId, expunge, details);
56405640

56415641
retrieveVmFromJobOutcome(outcome, String.valueOf(vmId), "restoreVirtualMachine");
56425642

@@ -5653,14 +5653,14 @@ public UserVm restoreVirtualMachine(final long vmId, final Long newTemplateId) t
56535653
}
56545654
}
56555655

5656-
private UserVm orchestrateRestoreVirtualMachine(final long vmId, final Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException {
5657-
s_logger.debug("Restoring vm " + vmId + " with new templateId " + newTemplateId);
5656+
private UserVm orchestrateRestoreVirtualMachine(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, final Map<String, String> details) throws ResourceUnavailableException, InsufficientCapacityException {
5657+
s_logger.debug("Restoring vm " + vmId + " with templateId : " + newTemplateId + " diskOfferingId : " + rootDiskOfferingId + " details : " + details);
56585658
final CallContext context = CallContext.current();
56595659
final Account account = context.getCallingAccount();
5660-
return _userVmService.restoreVirtualMachine(account, vmId, newTemplateId);
5660+
return _userVmService.restoreVirtualMachine(account, vmId, newTemplateId, rootDiskOfferingId, expunge, details);
56615661
}
56625662

5663-
public Outcome<VirtualMachine> restoreVirtualMachineThroughJobQueue(final long vmId, final Long newTemplateId) {
5663+
public Outcome<VirtualMachine> restoreVirtualMachineThroughJobQueue(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, Map<String, String> details) {
56645664
String commandName = VmWorkRestore.class.getName();
56655665
Pair<VmWorkJobVO, Long> pendingWorkJob = retrievePendingWorkJob(vmId, commandName);
56665666

@@ -5670,7 +5670,7 @@ public Outcome<VirtualMachine> restoreVirtualMachineThroughJobQueue(final long v
56705670
Pair<VmWorkJobVO, VmWork> newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId);
56715671

56725672
workJob = newVmWorkJobAndInfo.first();
5673-
VmWorkRestore workInfo = new VmWorkRestore(newVmWorkJobAndInfo.second(), newTemplateId);
5673+
VmWorkRestore workInfo = new VmWorkRestore(newVmWorkJobAndInfo.second(), newTemplateId, rootDiskOfferingId, expunge, details);
56745674

56755675
setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId);
56765676
}
@@ -5682,7 +5682,7 @@ public Outcome<VirtualMachine> restoreVirtualMachineThroughJobQueue(final long v
56825682
@ReflectionUse
56835683
private Pair<JobInfo.Status, String> orchestrateRestoreVirtualMachine(final VmWorkRestore work) throws Exception {
56845684
VMInstanceVO vm = findVmById(work.getVmId());
5685-
UserVm uservm = orchestrateRestoreVirtualMachine(vm.getId(), work.getTemplateId());
5685+
UserVm uservm = orchestrateRestoreVirtualMachine(vm.getId(), work.getTemplateId(), work.getRootDiskOfferingId(), work.getExpunge(), work.getDetails());
56865686
HashMap<Long, String> passwordMap = new HashMap<>();
56875687
passwordMap.put(uservm.getId(), uservm.getPassword());
56885688
return new Pair<>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(passwordMap));

engine/orchestration/src/main/java/com/cloud/vm/VmWorkRestore.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,38 @@
1616
// under the License.
1717
package com.cloud.vm;
1818

19+
import java.util.Map;
20+
1921
public class VmWorkRestore extends VmWork {
2022
private static final long serialVersionUID = 195901782359759635L;
2123

2224
private Long templateId;
25+
private Long rootDiskOfferingId;
26+
private Map<String,String> details;
2327

24-
public VmWorkRestore(long userId, long accountId, long vmId, String handlerName, Long templateId) {
25-
super(userId, accountId, vmId, handlerName);
28+
private boolean expunge;
2629

27-
this.templateId = templateId;
28-
}
29-
30-
public VmWorkRestore(VmWork vmWork, Long templateId) {
30+
public VmWorkRestore(VmWork vmWork, Long templateId, Long rootDiskOfferingId, boolean expunge, Map<String,String> details) {
3131
super(vmWork);
3232
this.templateId = templateId;
33+
this.rootDiskOfferingId = rootDiskOfferingId;
34+
this.expunge = expunge;
35+
this.details = details;
3336
}
3437

3538
public Long getTemplateId() {
3639
return templateId;
3740
}
41+
42+
public Long getRootDiskOfferingId() {
43+
return rootDiskOfferingId;
44+
}
45+
46+
public boolean getExpunge() {
47+
return expunge;
48+
}
49+
50+
public Map<String, String> getDetails() {
51+
return details;
52+
}
3853
}

engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161
import com.cloud.vm.dao.UserVmDetailsDao;
6262
import com.cloud.vm.dao.VMInstanceDao;
6363

64+
import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS;
65+
import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS;
66+
6467
@Component
6568
public class CloudOrchestrator implements OrchestrationService {
6669

@@ -196,8 +199,8 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String
196199
Map<String, String> userVmDetails = _userVmDetailsDao.listDetailsKeyPairs(vm.getId());
197200

198201
if (userVmDetails != null) {
199-
String minIops = userVmDetails.get("minIops");
200-
String maxIops = userVmDetails.get("maxIops");
202+
String minIops = userVmDetails.get(MIN_IOPS);
203+
String maxIops = userVmDetails.get(MAX_IOPS);
201204

202205
rootDiskOfferingInfo.setMinIops(minIops != null && minIops.trim().length() > 0 ? Long.parseLong(minIops) : null);
203206
rootDiskOfferingInfo.setMaxIops(maxIops != null && maxIops.trim().length() > 0 ? Long.parseLong(maxIops) : null);

engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -949,18 +949,7 @@ private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering
949949

950950
vol = _volsDao.persist(vol);
951951

952-
List<VolumeDetailVO> volumeDetailsVO = new ArrayList<VolumeDetailVO>();
953-
DiskOfferingDetailVO bandwidthLimitDetail = _diskOfferingDetailDao.findDetail(offering.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS);
954-
if (bandwidthLimitDetail != null) {
955-
volumeDetailsVO.add(new VolumeDetailVO(vol.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS, bandwidthLimitDetail.getValue(), false));
956-
}
957-
DiskOfferingDetailVO iopsLimitDetail = _diskOfferingDetailDao.findDetail(offering.getId(), Volume.IOPS_LIMIT);
958-
if (iopsLimitDetail != null) {
959-
volumeDetailsVO.add(new VolumeDetailVO(vol.getId(), Volume.IOPS_LIMIT, iopsLimitDetail.getValue(), false));
960-
}
961-
if (!volumeDetailsVO.isEmpty()) {
962-
_volDetailDao.saveDetails(volumeDetailsVO);
963-
}
952+
saveVolumeDetails(offering.getId(), vol.getId());
964953

965954
if (StringUtils.isNotBlank(configurationId)) {
966955
VolumeDetailVO deployConfigurationDetail = new VolumeDetailVO(vol.getId(), VmDetailConstants.DEPLOY_AS_IS_CONFIGURATION, configurationId, false);
@@ -985,6 +974,32 @@ private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering
985974
return toDiskProfile(vol, offering);
986975
}
987976

977+
@Override
978+
public void saveVolumeDetails(Long diskOfferingId, Long volumeId) {
979+
List<VolumeDetailVO> volumeDetailsVO = new ArrayList<>();
980+
DiskOfferingDetailVO bandwidthLimitDetail = _diskOfferingDetailDao.findDetail(diskOfferingId, Volume.BANDWIDTH_LIMIT_IN_MBPS);
981+
if (bandwidthLimitDetail != null) {
982+
volumeDetailsVO.add(new VolumeDetailVO(volumeId, Volume.BANDWIDTH_LIMIT_IN_MBPS, bandwidthLimitDetail.getValue(), false));
983+
} else {
984+
VolumeDetailVO bandwidthLimit = _volDetailDao.findDetail(volumeId, Volume.BANDWIDTH_LIMIT_IN_MBPS);
985+
if (bandwidthLimit != null) {
986+
_volDetailDao.remove(bandwidthLimit.getId());
987+
}
988+
}
989+
DiskOfferingDetailVO iopsLimitDetail = _diskOfferingDetailDao.findDetail(diskOfferingId, Volume.IOPS_LIMIT);
990+
if (iopsLimitDetail != null) {
991+
volumeDetailsVO.add(new VolumeDetailVO(volumeId, Volume.IOPS_LIMIT, iopsLimitDetail.getValue(), false));
992+
} else {
993+
VolumeDetailVO iopsLimit = _volDetailDao.findDetail(volumeId, Volume.IOPS_LIMIT);
994+
if (iopsLimit != null) {
995+
_volDetailDao.remove(iopsLimit.getId());
996+
}
997+
}
998+
if (!volumeDetailsVO.isEmpty()) {
999+
_volDetailDao.saveDetails(volumeDetailsVO);
1000+
}
1001+
}
1002+
9881003
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating ROOT volume", create = true)
9891004
@Override
9901005
public List<DiskProfile> allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm,

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1724,11 +1724,7 @@ public boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitio
17241724
return _volStateMachine.transitTo(vol, event, null, _volsDao);
17251725
}
17261726

1727-
@Override
1728-
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_DESTROY, eventDescription = "destroying a volume")
1729-
public Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge) {
1730-
VolumeVO volume = retrieveAndValidateVolume(volumeId, caller);
1731-
1727+
public void validateDestroyVolume(Volume volume, Account caller, boolean expunge, boolean forceExpunge) {
17321728
if (expunge) {
17331729
// When trying to expunge, permission is denied when the caller is not an admin and the AllowUserExpungeRecoverVolume is false for the caller.
17341730
final Long userId = caller.getAccountId();
@@ -1738,6 +1734,14 @@ public Volume destroyVolume(long volumeId, Account caller, boolean expunge, bool
17381734
} else if (volume.getState() == Volume.State.Allocated || volume.getState() == Volume.State.Uploaded) {
17391735
throw new InvalidParameterValueException("The volume in Allocated/Uploaded state can only be expunged not destroyed/recovered");
17401736
}
1737+
}
1738+
1739+
@Override
1740+
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_DESTROY, eventDescription = "destroying a volume")
1741+
public Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge) {
1742+
VolumeVO volume = retrieveAndValidateVolume(volumeId, caller);
1743+
1744+
validateDestroyVolume(volume, caller, expunge, forceExpunge);
17411745

17421746
destroyVolumeIfPossible(volume);
17431747

0 commit comments

Comments
 (0)