Skip to content

Commit 12497d0

Browse files
committed
Merge pull request #1727 from nvazquez/change-serv-offering
CLOUDSTACK-9539: Support changing Service offering for instance with VM Snapshots## Actual behaviour CloudStack doesn't support changing service offering for vm instances which have vm snapshots, they should be removed before changing service offering. ## Goal Extend actual behaviour by supporting changing service offering for vms which have vm snapshots. In that case, previously taken snapshots (if reverted) should use previous service offering, future snapshots should use the newest. ## Proposed solution: 1. Adding `service_offering_id` column on `vm_snapshots` table: This way snapshot can be reverted to original state even though service offering can be changed for vm instance. NOTE: Existing vm snapshots are populated on update script by: `UPDATE vm_snapshots s JOIN vm_instance v ON v.id = s.vm_id SET s.service_offering_id = v.service_offering_id;` 2. New vm snapshots will use instance vm service offering id as `service_offering_id` 3. Revert to vm snapshots should use vm snapshot's `service_offering_id` value. ## Example use case: - Deploy vm using service offering A - Take vm snapshot -> snap1 (service offering A) - Stop vm - Change vm service offering to B - Revert to VM snapshot snap 1 - Start vm It is expected that vm has service offering A after last step * pr/1727: CLOUDSTACK-9539: Support changing Service offering for instance with VM Snapshots Signed-off-by: Rajani Karuturi <[email protected]>
2 parents 238046f + 3a6d982 commit 12497d0

File tree

7 files changed

+635
-24
lines changed

7 files changed

+635
-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)