Skip to content

Commit 12c2c91

Browse files
committed
CLOUDSTACK-9539: Support changing Service offering for instance with VM Snapshots
1 parent e02003d commit 12c2c91

File tree

7 files changed

+634
-24
lines changed

7 files changed

+634
-24
lines changed

api/src/com/cloud/vm/snapshot/VMSnapshot.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,6 @@ enum Event {
102102

103103
@Override
104104
public long getAccountId();
105+
106+
public long getServiceOfferingId();
105107
}

engine/schema/src/com/cloud/vm/snapshot/VMSnapshotVO.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ public class VMSnapshotVO implements VMSnapshot {
7272
@Column(name = "domain_id")
7373
long domainId;
7474

75+
@Column(name = "service_offering_id")
76+
private long serviceOfferingId;
77+
7578
@Column(name = "vm_snapshot_type")
7679
@Enumerated(EnumType.STRING)
7780
VMSnapshot.Type type;
@@ -139,6 +142,7 @@ public VMSnapshotVO(Long accountId, Long domainId, Long vmId, String description
139142
displayName = vsDisplayName;
140143
this.type = type;
141144
this.current = current;
145+
this.serviceOfferingId = serviceOfferingId;
142146
}
143147

144148
@Override
@@ -248,4 +252,9 @@ public void setRemoved(Date removed) {
248252
public Class<?> getEntityType() {
249253
return VMSnapshot.class;
250254
}
255+
256+
@Override
257+
public long getServiceOfferingId() {
258+
return serviceOfferingId;
259+
}
251260
}

server/src/com/cloud/vm/UserVmManagerImpl.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -950,12 +950,6 @@ public UserVm upgradeVirtualMachine(UpgradeVMCmd cmd) throws ResourceAllocationE
950950
+ "; make sure the virtual machine is stopped");
951951
}
952952

953-
// If target VM has associated VM snapshots then don't allow upgrading of VM
954-
List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vmId);
955-
if (vmSnapshots.size() > 0) {
956-
throw new InvalidParameterValueException("Unable to change service offering for VM, please remove VM snapshots before changing service offering of VM");
957-
}
958-
959953
_accountMgr.checkAccess(caller, null, true, vmInstance);
960954

961955
// Check resource limits for CPU and Memory.
@@ -1616,11 +1610,6 @@ public boolean upgradeVirtualMachine(Long vmId, Long newServiceOfferingId, Map<S
16161610
VMInstanceVO vmInstance = _vmInstanceDao.findById(vmId);
16171611

16181612
if (vmInstance != null) {
1619-
// If target VM has associated VM snapshots then don't allow upgrading of VM
1620-
List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vmId);
1621-
if (vmSnapshots.size() > 0) {
1622-
throw new InvalidParameterValueException("Unable to scale VM, please remove VM snapshots before scaling VM");
1623-
}
16241613
if (vmInstance.getState().equals(State.Stopped)) {
16251614
upgradeStoppedVirtualMachine(vmId, newServiceOfferingId, customParameters);
16261615
return true;

server/src/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java

Lines changed: 158 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,16 @@
5757
import com.cloud.exception.ConcurrentOperationException;
5858
import com.cloud.exception.InsufficientCapacityException;
5959
import com.cloud.exception.InvalidParameterValueException;
60+
import com.cloud.exception.ManagementServerException;
6061
import com.cloud.exception.ResourceAllocationException;
6162
import com.cloud.exception.ResourceUnavailableException;
63+
import com.cloud.exception.VirtualMachineMigrationException;
6264
import com.cloud.gpu.GPU;
6365
import com.cloud.hypervisor.Hypervisor.HypervisorType;
6466
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
6567
import com.cloud.projects.Project.ListProjectResourcesCriteria;
68+
import com.cloud.service.ServiceOfferingVO;
69+
import com.cloud.service.dao.ServiceOfferingDao;
6670
import com.cloud.service.dao.ServiceOfferingDetailsDao;
6771
import com.cloud.storage.GuestOSVO;
6872
import com.cloud.storage.Snapshot;
@@ -89,7 +93,13 @@
8993
import com.cloud.utils.db.Filter;
9094
import com.cloud.utils.db.SearchBuilder;
9195
import com.cloud.utils.db.SearchCriteria;
96+
import com.cloud.utils.db.Transaction;
97+
import com.cloud.utils.db.TransactionCallbackWithException;
98+
import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn;
99+
import com.cloud.utils.db.TransactionStatus;
92100
import com.cloud.utils.exception.CloudRuntimeException;
101+
import com.cloud.vm.UserVmDetailVO;
102+
import com.cloud.vm.UserVmManager;
93103
import com.cloud.vm.UserVmVO;
94104
import com.cloud.vm.VMInstanceVO;
95105
import com.cloud.vm.VirtualMachine;
@@ -102,8 +112,10 @@
102112
import com.cloud.vm.VmWorkJobHandlerProxy;
103113
import com.cloud.vm.VmWorkSerializer;
104114
import com.cloud.vm.dao.UserVmDao;
115+
import com.cloud.vm.dao.UserVmDetailsDao;
105116
import com.cloud.vm.dao.VMInstanceDao;
106117
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
118+
import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao;
107119

108120
@Component
109121
public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase implements VMSnapshotManager, VMSnapshotService, VmWorkJobHandler {
@@ -135,6 +147,14 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
135147

136148
@Inject
137149
VmWorkJobDao _workJobDao;
150+
@Inject
151+
protected UserVmManager _userVmManager;
152+
@Inject
153+
protected ServiceOfferingDao _serviceOfferingDao;
154+
@Inject
155+
protected UserVmDetailsDao _userVmDetailsDao;
156+
@Inject
157+
protected VMSnapshotDetailsDao _vmSnapshotDetailsDao;
138158

139159
VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this);
140160

@@ -346,21 +366,65 @@ public VMSnapshot allocVMSnapshot(Long vmId, String vsDisplayName, String vsDesc
346366
vmSnapshotType = VMSnapshot.Type.DiskAndMemory;
347367

348368
try {
349-
VMSnapshotVO vmSnapshotVo =
350-
new VMSnapshotVO(userVmVo.getAccountId(), userVmVo.getDomainId(), vmId, vsDescription, vmSnapshotName, vsDisplayName, userVmVo.getServiceOfferingId(),
351-
vmSnapshotType, null);
352-
VMSnapshot vmSnapshot = _vmSnapshotDao.persist(vmSnapshotVo);
353-
if (vmSnapshot == null) {
354-
throw new CloudRuntimeException("Failed to create snapshot for vm: " + vmId);
355-
}
356-
return vmSnapshot;
369+
return createAndPersistVMSnapshot(userVmVo, vsDescription, vmSnapshotName, vsDisplayName, vmSnapshotType);
357370
} catch (Exception e) {
358371
String msg = e.getMessage();
359372
s_logger.error("Create vm snapshot record failed for vm: " + vmId + " due to: " + msg);
360373
}
361374
return null;
362375
}
363376

377+
/**
378+
* Create, persist and return vm snapshot for userVmVo with given parameters.
379+
* Persistence and support for custom service offerings are done on the same transaction
380+
* @param userVmVo user vm
381+
* @param vmId vm id
382+
* @param vsDescription vm description
383+
* @param vmSnapshotName vm snapshot name
384+
* @param vsDisplayName vm snapshot display name
385+
* @param vmSnapshotType vm snapshot type
386+
* @return vm snapshot
387+
* @throws CloudRuntimeException if vm snapshot couldn't be persisted
388+
*/
389+
protected VMSnapshot createAndPersistVMSnapshot(UserVmVO userVmVo, String vsDescription, String vmSnapshotName, String vsDisplayName, VMSnapshot.Type vmSnapshotType) {
390+
final Long vmId = userVmVo.getId();
391+
final Long serviceOfferingId = userVmVo.getServiceOfferingId();
392+
final VMSnapshotVO vmSnapshotVo =
393+
new VMSnapshotVO(userVmVo.getAccountId(), userVmVo.getDomainId(), vmId, vsDescription, vmSnapshotName, vsDisplayName, serviceOfferingId,
394+
vmSnapshotType, null);
395+
return Transaction.execute(new TransactionCallbackWithException<VMSnapshot, CloudRuntimeException>() {
396+
@Override
397+
public VMSnapshot doInTransaction(TransactionStatus status) {
398+
VMSnapshot vmSnapshot = _vmSnapshotDao.persist(vmSnapshotVo);
399+
if (vmSnapshot == null) {
400+
throw new CloudRuntimeException("Failed to create snapshot for vm: " + vmId);
401+
}
402+
addSupportForCustomServiceOffering(vmId, serviceOfferingId, vmSnapshot.getId());
403+
return vmSnapshot;
404+
}
405+
});
406+
}
407+
408+
/**
409+
* Add entries on vm_snapshot_details if service offering is dynamic. This will allow setting details when revert to vm snapshot
410+
* @param vmId vm id
411+
* @param serviceOfferingId service offering id
412+
* @param vmSnapshotId vm snapshot id
413+
*/
414+
protected void addSupportForCustomServiceOffering(long vmId, long serviceOfferingId, long vmSnapshotId) {
415+
ServiceOfferingVO serviceOfferingVO = _serviceOfferingDao.findById(serviceOfferingId);
416+
if (serviceOfferingVO.isDynamic()) {
417+
List<UserVmDetailVO> vmDetails = _userVmDetailsDao.listDetails(vmId);
418+
List<VMSnapshotDetailsVO> vmSnapshotDetails = new ArrayList<VMSnapshotDetailsVO>();
419+
for (UserVmDetailVO detail : vmDetails) {
420+
if(detail.getName().equalsIgnoreCase("cpuNumber") || detail.getName().equalsIgnoreCase("cpuSpeed") || detail.getName().equalsIgnoreCase("memory")) {
421+
vmSnapshotDetails.add(new VMSnapshotDetailsVO(vmSnapshotId, detail.getName(), detail.getValue(), detail.isDisplay()));
422+
}
423+
}
424+
_vmSnapshotDetailsDao.saveDetails(vmSnapshotDetails);
425+
}
426+
}
427+
364428
@Override
365429
public String getName() {
366430
return _name;
@@ -654,16 +718,76 @@ else if (jobResult instanceof Throwable)
654718
}
655719
}
656720

721+
/**
722+
* If snapshot was taken with a different service offering than actual used in vm, should change it back to it
723+
* @param userVm vm to change service offering (if necessary)
724+
* @param vmSnapshotVo vm snapshot
725+
*/
726+
protected void updateUserVmServiceOffering(UserVm userVm, VMSnapshotVO vmSnapshotVo) {
727+
if (vmSnapshotVo.getServiceOfferingId() != userVm.getServiceOfferingId()) {
728+
changeUserVmServiceOffering(userVm, vmSnapshotVo);
729+
}
730+
}
731+
732+
/**
733+
* Get user vm details as a map
734+
* @param userVm user vm
735+
* @return map
736+
*/
737+
protected Map<String, String> getVmMapDetails(UserVm userVm) {
738+
List<UserVmDetailVO> userVmDetails = _userVmDetailsDao.listDetails(userVm.getId());
739+
Map<String, String> details = new HashMap<String, String>();
740+
for (UserVmDetailVO detail : userVmDetails) {
741+
details.put(detail.getName(), detail.getValue());
742+
}
743+
return details;
744+
}
745+
746+
/**
747+
* Update service offering on {@link userVm} to the one specified in {@link vmSnapshotVo}
748+
* @param userVm user vm to be updated
749+
* @param vmSnapshotVo vm snapshot
750+
*/
751+
protected void changeUserVmServiceOffering(UserVm userVm, VMSnapshotVO vmSnapshotVo) {
752+
Map<String, String> vmDetails = getVmMapDetails(userVm);
753+
boolean result = upgradeUserVmServiceOffering(userVm.getId(), vmSnapshotVo.getServiceOfferingId(), vmDetails);
754+
if (! result){
755+
throw new CloudRuntimeException("VM Snapshot reverting failed due to vm service offering couldn't be changed to the one used when snapshot was taken");
756+
}
757+
s_logger.debug("Successfully changed service offering to " + vmSnapshotVo.getServiceOfferingId() + " for vm " + userVm.getId());
758+
}
759+
760+
/**
761+
* Upgrade virtual machine {@linkplain vmId} to new service offering {@linkplain serviceOfferingId}
762+
* @param vmId vm id
763+
* @param serviceOfferingId service offering id
764+
* @param details vm details
765+
* @return if operation was successful
766+
*/
767+
protected boolean upgradeUserVmServiceOffering(Long vmId, Long serviceOfferingId, Map<String, String> details) {
768+
boolean result;
769+
try {
770+
result = _userVmManager.upgradeVirtualMachine(vmId, serviceOfferingId, details);
771+
if (! result){
772+
s_logger.error("Couldn't change service offering for vm " + vmId + " to " + serviceOfferingId);
773+
}
774+
return result;
775+
} catch (ConcurrentOperationException | ResourceUnavailableException | ManagementServerException | VirtualMachineMigrationException e) {
776+
s_logger.error("Couldn't change service offering for vm " + vmId + " to " + serviceOfferingId + " due to: " + e.getMessage());
777+
return false;
778+
}
779+
}
780+
657781
private UserVm orchestrateRevertToVMSnapshot(Long vmSnapshotId) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException {
658782

659783
// check if VM snapshot exists in DB
660-
VMSnapshotVO vmSnapshotVo = _vmSnapshotDao.findById(vmSnapshotId);
784+
final VMSnapshotVO vmSnapshotVo = _vmSnapshotDao.findById(vmSnapshotId);
661785
if (vmSnapshotVo == null) {
662786
throw new InvalidParameterValueException(
663787
"unable to find the vm snapshot with id " + vmSnapshotId);
664788
}
665789
Long vmId = vmSnapshotVo.getVmId();
666-
UserVmVO userVm = _userVMDao.findById(vmId);
790+
final UserVmVO userVm = _userVMDao.findById(vmId);
667791
// check if VM exists
668792
if (userVm == null) {
669793
throw new InvalidParameterValueException("Revert vm to snapshot: "
@@ -721,13 +845,37 @@ private UserVm orchestrateRevertToVMSnapshot(Long vmSnapshotId) throws Insuffici
721845
try {
722846
VMSnapshotStrategy strategy = findVMSnapshotStrategy(vmSnapshotVo);
723847
strategy.revertVMSnapshot(vmSnapshotVo);
848+
Transaction.execute(new TransactionCallbackWithExceptionNoReturn<CloudRuntimeException>() {
849+
@Override
850+
public void doInTransactionWithoutResult(TransactionStatus status) throws CloudRuntimeException {
851+
revertUserVmDetailsFromVmSnapshot(userVm, vmSnapshotVo);
852+
updateUserVmServiceOffering(userVm, vmSnapshotVo);
853+
}
854+
});
724855
return userVm;
725856
} catch (Exception e) {
726857
s_logger.debug("Failed to revert vmsnapshot: " + vmSnapshotId, e);
727858
throw new CloudRuntimeException(e.getMessage());
728859
}
729860
}
730861

862+
/**
863+
* Update or add user vm details from vm snapshot for vms with custom service offerings
864+
* @param userVm user vm
865+
* @param vmSnapshotVo vm snapshot
866+
*/
867+
protected void revertUserVmDetailsFromVmSnapshot(UserVmVO userVm, VMSnapshotVO vmSnapshotVo) {
868+
ServiceOfferingVO serviceOfferingVO = _serviceOfferingDao.findById(vmSnapshotVo.getServiceOfferingId());
869+
if (serviceOfferingVO.isDynamic()) {
870+
List<VMSnapshotDetailsVO> vmSnapshotDetails = _vmSnapshotDetailsDao.listDetails(vmSnapshotVo.getId());
871+
List<UserVmDetailVO> userVmDetails = new ArrayList<UserVmDetailVO>();
872+
for (VMSnapshotDetailsVO detail : vmSnapshotDetails) {
873+
userVmDetails.add(new UserVmDetailVO(userVm.getId(), detail.getName(), detail.getValue(), detail.isDisplay()));
874+
}
875+
_userVmDetailsDao.saveDetails(userVmDetails);
876+
}
877+
}
878+
731879
@Override
732880
public RestoreVMSnapshotCommand createRestoreCommand(UserVmVO userVm, List<VMSnapshotVO> vmSnapshotVOs) {
733881
if (!HypervisorType.KVM.equals(userVm.getHypervisorType()))

0 commit comments

Comments
 (0)