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 <rajani.karuturi@accelerite.com>
This commit is contained in:
Rajani Karuturi 2017-02-17 12:08:25 +05:30
commit 12497d04bf
7 changed files with 635 additions and 24 deletions

View File

@ -102,4 +102,6 @@ public interface VMSnapshot extends ControlledEntity, Identity, InternalIdentity
@Override
public long getAccountId();
public long getServiceOfferingId();
}

View File

@ -72,6 +72,9 @@ public class VMSnapshotVO implements VMSnapshot {
@Column(name = "domain_id")
long domainId;
@Column(name = "service_offering_id")
private long serviceOfferingId;
@Column(name = "vm_snapshot_type")
@Enumerated(EnumType.STRING)
VMSnapshot.Type type;
@ -139,6 +142,7 @@ public class VMSnapshotVO implements VMSnapshot {
displayName = vsDisplayName;
this.type = type;
this.current = current;
this.serviceOfferingId = serviceOfferingId;
}
@Override
@ -248,4 +252,9 @@ public class VMSnapshotVO implements VMSnapshot {
public Class<?> getEntityType() {
return VMSnapshot.class;
}
@Override
public long getServiceOfferingId() {
return serviceOfferingId;
}
}

View File

@ -950,12 +950,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
+ "; make sure the virtual machine is stopped");
}
// If target VM has associated VM snapshots then don't allow upgrading of VM
List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vmId);
if (vmSnapshots.size() > 0) {
throw new InvalidParameterValueException("Unable to change service offering for VM, please remove VM snapshots before changing service offering of VM");
}
_accountMgr.checkAccess(caller, null, true, vmInstance);
// Check resource limits for CPU and Memory.
@ -1616,11 +1610,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
VMInstanceVO vmInstance = _vmInstanceDao.findById(vmId);
if (vmInstance != null) {
// If target VM has associated VM snapshots then don't allow upgrading of VM
List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vmId);
if (vmSnapshots.size() > 0) {
throw new InvalidParameterValueException("Unable to scale VM, please remove VM snapshots before scaling VM");
}
if (vmInstance.getState().equals(State.Stopped)) {
upgradeStoppedVirtualMachine(vmId, newServiceOfferingId, customParameters);
return true;

View File

@ -57,12 +57,16 @@ import com.cloud.event.EventTypes;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ManagementServerException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.exception.VirtualMachineMigrationException;
import com.cloud.gpu.GPU;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
import com.cloud.projects.Project.ListProjectResourcesCriteria;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.service.dao.ServiceOfferingDetailsDao;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.Snapshot;
@ -89,7 +93,13 @@ import com.cloud.utils.db.EntityManager;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallbackWithException;
import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.UserVmDetailVO;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
@ -102,8 +112,10 @@ import com.cloud.vm.VmWorkJobHandler;
import com.cloud.vm.VmWorkJobHandlerProxy;
import com.cloud.vm.VmWorkSerializer;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.UserVmDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao;
@Component
public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase implements VMSnapshotManager, VMSnapshotService, VmWorkJobHandler {
@ -135,6 +147,14 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
@Inject
VmWorkJobDao _workJobDao;
@Inject
protected UserVmManager _userVmManager;
@Inject
protected ServiceOfferingDao _serviceOfferingDao;
@Inject
protected UserVmDetailsDao _userVmDetailsDao;
@Inject
protected VMSnapshotDetailsDao _vmSnapshotDetailsDao;
VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this);
@ -346,14 +366,7 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
vmSnapshotType = VMSnapshot.Type.DiskAndMemory;
try {
VMSnapshotVO vmSnapshotVo =
new VMSnapshotVO(userVmVo.getAccountId(), userVmVo.getDomainId(), vmId, vsDescription, vmSnapshotName, vsDisplayName, userVmVo.getServiceOfferingId(),
vmSnapshotType, null);
VMSnapshot vmSnapshot = _vmSnapshotDao.persist(vmSnapshotVo);
if (vmSnapshot == null) {
throw new CloudRuntimeException("Failed to create snapshot for vm: " + vmId);
}
return vmSnapshot;
return createAndPersistVMSnapshot(userVmVo, vsDescription, vmSnapshotName, vsDisplayName, vmSnapshotType);
} catch (Exception e) {
String msg = e.getMessage();
s_logger.error("Create vm snapshot record failed for vm: " + vmId + " due to: " + msg);
@ -361,6 +374,57 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
return null;
}
/**
* Create, persist and return vm snapshot for userVmVo with given parameters.
* Persistence and support for custom service offerings are done on the same transaction
* @param userVmVo user vm
* @param vmId vm id
* @param vsDescription vm description
* @param vmSnapshotName vm snapshot name
* @param vsDisplayName vm snapshot display name
* @param vmSnapshotType vm snapshot type
* @return vm snapshot
* @throws CloudRuntimeException if vm snapshot couldn't be persisted
*/
protected VMSnapshot createAndPersistVMSnapshot(UserVmVO userVmVo, String vsDescription, String vmSnapshotName, String vsDisplayName, VMSnapshot.Type vmSnapshotType) {
final Long vmId = userVmVo.getId();
final Long serviceOfferingId = userVmVo.getServiceOfferingId();
final VMSnapshotVO vmSnapshotVo =
new VMSnapshotVO(userVmVo.getAccountId(), userVmVo.getDomainId(), vmId, vsDescription, vmSnapshotName, vsDisplayName, serviceOfferingId,
vmSnapshotType, null);
return Transaction.execute(new TransactionCallbackWithException<VMSnapshot, CloudRuntimeException>() {
@Override
public VMSnapshot doInTransaction(TransactionStatus status) {
VMSnapshot vmSnapshot = _vmSnapshotDao.persist(vmSnapshotVo);
if (vmSnapshot == null) {
throw new CloudRuntimeException("Failed to create snapshot for vm: " + vmId);
}
addSupportForCustomServiceOffering(vmId, serviceOfferingId, vmSnapshot.getId());
return vmSnapshot;
}
});
}
/**
* Add entries on vm_snapshot_details if service offering is dynamic. This will allow setting details when revert to vm snapshot
* @param vmId vm id
* @param serviceOfferingId service offering id
* @param vmSnapshotId vm snapshot id
*/
protected void addSupportForCustomServiceOffering(long vmId, long serviceOfferingId, long vmSnapshotId) {
ServiceOfferingVO serviceOfferingVO = _serviceOfferingDao.findById(serviceOfferingId);
if (serviceOfferingVO.isDynamic()) {
List<UserVmDetailVO> vmDetails = _userVmDetailsDao.listDetails(vmId);
List<VMSnapshotDetailsVO> vmSnapshotDetails = new ArrayList<VMSnapshotDetailsVO>();
for (UserVmDetailVO detail : vmDetails) {
if(detail.getName().equalsIgnoreCase("cpuNumber") || detail.getName().equalsIgnoreCase("cpuSpeed") || detail.getName().equalsIgnoreCase("memory")) {
vmSnapshotDetails.add(new VMSnapshotDetailsVO(vmSnapshotId, detail.getName(), detail.getValue(), detail.isDisplay()));
}
}
_vmSnapshotDetailsDao.saveDetails(vmSnapshotDetails);
}
}
@Override
public String getName() {
return _name;
@ -654,16 +718,76 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
}
}
/**
* If snapshot was taken with a different service offering than actual used in vm, should change it back to it
* @param userVm vm to change service offering (if necessary)
* @param vmSnapshotVo vm snapshot
*/
protected void updateUserVmServiceOffering(UserVm userVm, VMSnapshotVO vmSnapshotVo) {
if (vmSnapshotVo.getServiceOfferingId() != userVm.getServiceOfferingId()) {
changeUserVmServiceOffering(userVm, vmSnapshotVo);
}
}
/**
* Get user vm details as a map
* @param userVm user vm
* @return map
*/
protected Map<String, String> getVmMapDetails(UserVm userVm) {
List<UserVmDetailVO> userVmDetails = _userVmDetailsDao.listDetails(userVm.getId());
Map<String, String> details = new HashMap<String, String>();
for (UserVmDetailVO detail : userVmDetails) {
details.put(detail.getName(), detail.getValue());
}
return details;
}
/**
* Update service offering on {@link userVm} to the one specified in {@link vmSnapshotVo}
* @param userVm user vm to be updated
* @param vmSnapshotVo vm snapshot
*/
protected void changeUserVmServiceOffering(UserVm userVm, VMSnapshotVO vmSnapshotVo) {
Map<String, String> vmDetails = getVmMapDetails(userVm);
boolean result = upgradeUserVmServiceOffering(userVm.getId(), vmSnapshotVo.getServiceOfferingId(), vmDetails);
if (! result){
throw new CloudRuntimeException("VM Snapshot reverting failed due to vm service offering couldn't be changed to the one used when snapshot was taken");
}
s_logger.debug("Successfully changed service offering to " + vmSnapshotVo.getServiceOfferingId() + " for vm " + userVm.getId());
}
/**
* Upgrade virtual machine {@linkplain vmId} to new service offering {@linkplain serviceOfferingId}
* @param vmId vm id
* @param serviceOfferingId service offering id
* @param details vm details
* @return if operation was successful
*/
protected boolean upgradeUserVmServiceOffering(Long vmId, Long serviceOfferingId, Map<String, String> details) {
boolean result;
try {
result = _userVmManager.upgradeVirtualMachine(vmId, serviceOfferingId, details);
if (! result){
s_logger.error("Couldn't change service offering for vm " + vmId + " to " + serviceOfferingId);
}
return result;
} catch (ConcurrentOperationException | ResourceUnavailableException | ManagementServerException | VirtualMachineMigrationException e) {
s_logger.error("Couldn't change service offering for vm " + vmId + " to " + serviceOfferingId + " due to: " + e.getMessage());
return false;
}
}
private UserVm orchestrateRevertToVMSnapshot(Long vmSnapshotId) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException {
// check if VM snapshot exists in DB
VMSnapshotVO vmSnapshotVo = _vmSnapshotDao.findById(vmSnapshotId);
final VMSnapshotVO vmSnapshotVo = _vmSnapshotDao.findById(vmSnapshotId);
if (vmSnapshotVo == null) {
throw new InvalidParameterValueException(
"unable to find the vm snapshot with id " + vmSnapshotId);
}
Long vmId = vmSnapshotVo.getVmId();
UserVmVO userVm = _userVMDao.findById(vmId);
final UserVmVO userVm = _userVMDao.findById(vmId);
// check if VM exists
if (userVm == null) {
throw new InvalidParameterValueException("Revert vm to snapshot: "
@ -721,6 +845,13 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
try {
VMSnapshotStrategy strategy = findVMSnapshotStrategy(vmSnapshotVo);
strategy.revertVMSnapshot(vmSnapshotVo);
Transaction.execute(new TransactionCallbackWithExceptionNoReturn<CloudRuntimeException>() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) throws CloudRuntimeException {
revertUserVmDetailsFromVmSnapshot(userVm, vmSnapshotVo);
updateUserVmServiceOffering(userVm, vmSnapshotVo);
}
});
return userVm;
} catch (Exception e) {
s_logger.debug("Failed to revert vmsnapshot: " + vmSnapshotId, e);
@ -728,6 +859,23 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
}
}
/**
* Update or add user vm details from vm snapshot for vms with custom service offerings
* @param userVm user vm
* @param vmSnapshotVo vm snapshot
*/
protected void revertUserVmDetailsFromVmSnapshot(UserVmVO userVm, VMSnapshotVO vmSnapshotVo) {
ServiceOfferingVO serviceOfferingVO = _serviceOfferingDao.findById(vmSnapshotVo.getServiceOfferingId());
if (serviceOfferingVO.isDynamic()) {
List<VMSnapshotDetailsVO> vmSnapshotDetails = _vmSnapshotDetailsDao.listDetails(vmSnapshotVo.getId());
List<UserVmDetailVO> userVmDetails = new ArrayList<UserVmDetailVO>();
for (VMSnapshotDetailsVO detail : vmSnapshotDetails) {
userVmDetails.add(new UserVmDetailVO(userVm.getId(), detail.getName(), detail.getValue(), detail.isDisplay()));
}
_userVmDetailsDao.saveDetails(userVmDetails);
}
}
@Override
public RestoreVMSnapshotCommand createRestoreCommand(UserVmVO userVm, List<VMSnapshotVO> vmSnapshotVOs) {
if (!HypervisorType.KVM.equals(userVm.getHypervisorType()))

View File

@ -16,6 +16,7 @@
// under the License.
package com.cloud.vm.snapshot;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
@ -23,30 +24,45 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.never;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.ResourceDetail;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import com.cloud.agent.AgentManager;
import com.cloud.exception.AgentUnavailableException;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ManagementServerException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.exception.VirtualMachineMigrationException;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.HypervisorGuruManager;
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.service.dao.ServiceOfferingDetailsDao;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.Snapshot;
@ -59,14 +75,19 @@ import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.UserDao;
import com.cloud.uservm.UserVm;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
import com.cloud.vm.UserVmDetailVO;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.UserVmDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao;
public class VMSnapshotManagerTest {
@Spy
@ -107,13 +128,52 @@ public class VMSnapshotManagerTest {
HypervisorCapabilitiesDao _hypervisorCapabilitiesDao;
@Mock
ServiceOfferingDetailsDao _serviceOfferingDetailsDao;
@Mock
ServiceOfferingDao _serviceOfferingDao;
@Mock
UserVmDetailsDao _userVmDetailsDao;
@Mock
VMSnapshotDetailsDao _vmSnapshotDetailsDao;
@Mock
UserVmManager _userVmManager;
int _vmSnapshotMax = 10;
private static final long TEST_VM_ID = 3L;
private static final long SERVICE_OFFERING_ID = 1L;
private static final long SERVICE_OFFERING_DIFFERENT_ID = 2L;
private static VMSnapshot.Type vmSnapshotType;
private static List<UserVmDetailVO> userVmDetails;
private static List<VMSnapshotDetailsVO> vmSnapshotDetails;
private static final long VM_SNAPSHOT_ID = 1L;
private static final String VM_SNAPSHOT_NAME = "Vm-Snapshot-Name";
private static final String VM_SNAPSHOT_DESCRIPTION = "Vm-Snapshot-Desc";
private static final String VM_SNAPSHOT_DISPLAY_NAME = "Vm-Snapshot-Display-Name";
@Mock
UserVmVO vmMock;
@Mock
VolumeVO volumeMock;
@Mock
VMSnapshotVO vmSnapshotVO;
@Mock
ServiceOfferingVO serviceOffering;
@Mock
UserVmDetailVO userVmDetailCpuNumber;
@Mock
UserVmDetailVO userVmDetailMemory;
@Mock
VMSnapshotDetailsVO vmSnapshotDetailCpuNumber;
@Mock
VMSnapshotDetailsVO vmSnapshotDetailMemory;
@Mock
UserVm userVm;
@Captor
ArgumentCaptor<List<VMSnapshotDetailsVO>> listVmSnapshotDetailsCaptor;
@Captor
ArgumentCaptor<Map<String,String>> mapDetailsCaptor;
@Captor
ArgumentCaptor<List<UserVmDetailVO>> listUserVmDetailsCaptor;
@Before
public void setup() {
@ -133,6 +193,11 @@ public class VMSnapshotManagerTest {
_vmSnapshotMgr._vmSnapshotMax = _vmSnapshotMax;
_vmSnapshotMgr._serviceOfferingDao = _serviceOfferingDao;
_vmSnapshotMgr._userVmDetailsDao = _userVmDetailsDao;
_vmSnapshotMgr._vmSnapshotDetailsDao = _vmSnapshotDetailsDao;
_vmSnapshotMgr._userVmManager = _userVmManager;
when(_userVMDao.findById(anyLong())).thenReturn(vmMock);
when(_vmSnapshotDao.findByName(anyLong(), anyString())).thenReturn(null);
when(_vmSnapshotDao.findByVm(anyLong())).thenReturn(new ArrayList<VMSnapshotVO>());
@ -144,10 +209,40 @@ public class VMSnapshotManagerTest {
when(volumeMock.getInstanceId()).thenReturn(TEST_VM_ID);
when(_volumeDao.findByInstance(anyLong())).thenReturn(mockVolumeList);
when(vmMock.getId()).thenReturn(TEST_VM_ID);
when(vmMock.getServiceOfferingId()).thenReturn(SERVICE_OFFERING_ID);
when(vmMock.getAccountId()).thenReturn(1L);
when(vmMock.getDomainId()).thenReturn(1L);
when(vmMock.getInstanceName()).thenReturn("i-3-VM-TEST");
when(vmMock.getState()).thenReturn(State.Running);
when(vmMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.XenServer);
when(_guestOSDao.findById(anyLong())).thenReturn(mock(GuestOSVO.class));
when(vmSnapshotVO.getId()).thenReturn(VM_SNAPSHOT_ID);
when(serviceOffering.isDynamic()).thenReturn(false);
when(_serviceOfferingDao.findById(SERVICE_OFFERING_ID)).thenReturn(serviceOffering);
for (ResourceDetail detail : Arrays.asList(userVmDetailCpuNumber, vmSnapshotDetailCpuNumber)) {
when(detail.getName()).thenReturn("cpuNumber");
when(detail.getValue()).thenReturn("2");
when(detail.isDisplay()).thenReturn(true);
}
for (ResourceDetail detail : Arrays.asList(userVmDetailMemory, vmSnapshotDetailMemory)) {
when(detail.getName()).thenReturn("memory");
when(detail.getValue()).thenReturn("2048");
when(detail.isDisplay()).thenReturn(true);
}
userVmDetails = Arrays.asList(userVmDetailCpuNumber, userVmDetailMemory);
vmSnapshotDetails = Arrays.asList(vmSnapshotDetailCpuNumber, vmSnapshotDetailMemory);
when(_userVmDetailsDao.listDetails(TEST_VM_ID)).thenReturn(userVmDetails);
when(_vmSnapshotDetailsDao.listDetails(VM_SNAPSHOT_ID)).thenReturn(vmSnapshotDetails);
when(userVm.getId()).thenReturn(TEST_VM_ID);
when(userVm.getServiceOfferingId()).thenReturn(SERVICE_OFFERING_ID);
when(vmSnapshotVO.getServiceOfferingId()).thenReturn(SERVICE_OFFERING_ID);
}
// vmId null case
@ -207,4 +302,104 @@ public class VMSnapshotManagerTest {
}
@Test
public void testCreateAndPersistVMSnapshot() {
when(_vmSnapshotDao.persist(any(VMSnapshotVO.class))).thenReturn(vmSnapshotVO);
_vmSnapshotMgr.createAndPersistVMSnapshot(vmMock, VM_SNAPSHOT_DESCRIPTION,
VM_SNAPSHOT_NAME, VM_SNAPSHOT_DISPLAY_NAME, vmSnapshotType);
verify(_vmSnapshotMgr).addSupportForCustomServiceOffering(TEST_VM_ID, SERVICE_OFFERING_ID, VM_SNAPSHOT_ID);
}
@Test(expected=CloudRuntimeException.class)
public void testCreateAndPersistVMSnapshotNullVMSnapshot() {
when(_vmSnapshotDao.persist(any(VMSnapshotVO.class))).thenReturn(null);
_vmSnapshotMgr.createAndPersistVMSnapshot(vmMock, VM_SNAPSHOT_DESCRIPTION,
VM_SNAPSHOT_NAME, VM_SNAPSHOT_DISPLAY_NAME, vmSnapshotType);
}
@Test
public void testAddSupportForCustomServiceOfferingNotDynamicServiceOffering() {
_vmSnapshotMgr.addSupportForCustomServiceOffering(TEST_VM_ID, SERVICE_OFFERING_ID, VM_SNAPSHOT_ID);
verify(_userVmDetailsDao, never()).listDetails(TEST_VM_ID);
}
@Test
public void testAddSupportForCustomServiceOfferingDynamicServiceOffering() {
when(serviceOffering.isDynamic()).thenReturn(true);
_vmSnapshotMgr.addSupportForCustomServiceOffering(TEST_VM_ID, SERVICE_OFFERING_ID, VM_SNAPSHOT_ID);
verify(_userVmDetailsDao).listDetails(TEST_VM_ID);
verify(_vmSnapshotDetailsDao).saveDetails(listVmSnapshotDetailsCaptor.capture());
}
@Test
public void testUpdateUserVmServiceOfferingSameServiceOffering() {
_vmSnapshotMgr.updateUserVmServiceOffering(userVm, vmSnapshotVO);
verify(_vmSnapshotMgr, never()).changeUserVmServiceOffering(userVm, vmSnapshotVO);
}
@Test
public void testUpdateUserVmServiceOfferingDifferentServiceOffering() throws ConcurrentOperationException, ResourceUnavailableException, ManagementServerException, VirtualMachineMigrationException {
when(userVm.getServiceOfferingId()).thenReturn(SERVICE_OFFERING_DIFFERENT_ID);
when(_userVmManager.upgradeVirtualMachine(Matchers.eq(TEST_VM_ID), Matchers.eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture())).thenReturn(true);
_vmSnapshotMgr.updateUserVmServiceOffering(userVm, vmSnapshotVO);
verify(_vmSnapshotMgr).changeUserVmServiceOffering(userVm, vmSnapshotVO);
verify(_vmSnapshotMgr).getVmMapDetails(userVm);
verify(_vmSnapshotMgr).upgradeUserVmServiceOffering(Matchers.eq(TEST_VM_ID), Matchers.eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture());
}
@Test
public void testGetVmMapDetails() {
Map<String, String> result = _vmSnapshotMgr.getVmMapDetails(userVm);
assert(result.containsKey(userVmDetailCpuNumber.getName()));
assert(result.containsKey(userVmDetailMemory.getName()));
assertEquals(userVmDetails.size(), result.size());
assertEquals(userVmDetailCpuNumber.getValue(), result.get(userVmDetailCpuNumber.getName()));
assertEquals(userVmDetailMemory.getValue(), result.get(userVmDetailMemory.getName()));
}
@Test
public void testChangeUserVmServiceOffering() throws ConcurrentOperationException, ResourceUnavailableException, ManagementServerException, VirtualMachineMigrationException {
when(_userVmManager.upgradeVirtualMachine(Matchers.eq(TEST_VM_ID), Matchers.eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture())).thenReturn(true);
_vmSnapshotMgr.changeUserVmServiceOffering(userVm, vmSnapshotVO);
verify(_vmSnapshotMgr).getVmMapDetails(userVm);
verify(_vmSnapshotMgr).upgradeUserVmServiceOffering(Matchers.eq(TEST_VM_ID), Matchers.eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture());
}
@Test(expected=CloudRuntimeException.class)
public void testChangeUserVmServiceOfferingFailOnUpgradeVMServiceOffering() throws ConcurrentOperationException, ResourceUnavailableException, ManagementServerException, VirtualMachineMigrationException {
when(_userVmManager.upgradeVirtualMachine(Matchers.eq(TEST_VM_ID), Matchers.eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture())).thenReturn(false);
_vmSnapshotMgr.changeUserVmServiceOffering(userVm, vmSnapshotVO);
verify(_vmSnapshotMgr).getVmMapDetails(userVm);
verify(_vmSnapshotMgr).upgradeUserVmServiceOffering(Matchers.eq(TEST_VM_ID), Matchers.eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture());
}
@Test
public void testUpgradeUserVmServiceOffering() throws ConcurrentOperationException, ResourceUnavailableException, ManagementServerException, VirtualMachineMigrationException {
Map<String, String> details = new HashMap<String, String>() {{
put(userVmDetailCpuNumber.getName(), userVmDetailCpuNumber.getValue());
put(userVmDetailMemory.getName(), userVmDetailMemory.getValue());
}};
when(_userVmManager.upgradeVirtualMachine(TEST_VM_ID, SERVICE_OFFERING_ID, details)).thenReturn(true);
_vmSnapshotMgr.upgradeUserVmServiceOffering(TEST_VM_ID, SERVICE_OFFERING_ID, details);
verify(_userVmManager).upgradeVirtualMachine(TEST_VM_ID, SERVICE_OFFERING_ID, details);
}
@Test
public void testRevertUserVmDetailsFromVmSnapshotNotDynamicServiceOffering() {
_vmSnapshotMgr.revertUserVmDetailsFromVmSnapshot(vmMock, vmSnapshotVO);
verify(_vmSnapshotDetailsDao, never()).listDetails(anyLong());
}
@Test
public void testRevertUserVmDetailsFromVmSnapshotDynamicServiceOffering() {
when(serviceOffering.isDynamic()).thenReturn(true);
_vmSnapshotMgr.revertUserVmDetailsFromVmSnapshot(vmMock, vmSnapshotVO);
verify(_vmSnapshotDetailsDao).listDetails(VM_SNAPSHOT_ID);
verify(_userVmDetailsDao).saveDetails(listUserVmDetailsCaptor.capture());
}
}

View File

@ -215,3 +215,17 @@ VIEW `image_store_view` AS
FROM
(`image_store`
LEFT JOIN `data_center` ON ((`image_store`.`data_center_id` = `data_center`.`id`)));
-- Add service_offering_id column to vm_snapshots table
ALTER TABLE `cloud`.`vm_snapshots` ADD COLUMN `service_offering_id` BIGINT(20) UNSIGNED NOT NULL COMMENT '' AFTER `domain_id`;
UPDATE `cloud`.`vm_snapshots` s JOIN `cloud`.`vm_instance` v ON v.id = s.vm_id SET s.service_offering_id = v.service_offering_id;
ALTER TABLE `cloud`.`vm_snapshots` ADD CONSTRAINT `fk_vm_snapshots_service_offering_id` FOREIGN KEY (`service_offering_id`) REFERENCES `cloud`.`service_offering` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
-- Update vm snapshot details for instances with custom service offerings
INSERT INTO `cloud`.`vm_snapshot_details` (vm_snapshot_id, name, value)
SELECT s.id, d.name, d.value
FROM `cloud`.`user_vm_details` d JOIN `cloud`.`vm_instance` v ON (d.vm_id = v.id)
JOIN `cloud`.`service_offering` o ON (v.service_offering_id = o.id)
JOIN `cloud`.`vm_snapshots` s ON (s.service_offering_id = o.id AND s.vm_id = v.id)
WHERE (o.cpu is null AND o.speed IS NULL AND o.ram_size IS NULL) AND
(d.name = 'cpuNumber' OR d.name = 'cpuSpeed' OR d.name = 'memory');

View File

@ -16,10 +16,10 @@
# under the License.
# Import Local Modules
from marvin.codes import FAILED, KVM, PASS, XEN_SERVER
from marvin.codes import FAILED, KVM, PASS, XEN_SERVER, RUNNING
from nose.plugins.attrib import attr
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.lib.utils import random_gen, cleanup_resources, validateList, is_snapshot_on_nfs
from marvin.lib.utils import random_gen, cleanup_resources, validateList, is_snapshot_on_nfs, isAlmostEqual
from marvin.lib.base import (Account,
ServiceOffering,
VirtualMachine,
@ -29,7 +29,8 @@ from marvin.lib.base import (Account,
from marvin.lib.common import (get_zone,
get_domain,
get_template,
list_snapshots)
list_snapshots,
list_virtual_machines)
import time
@ -407,3 +408,256 @@ class TestSnapshots(cloudstackTestCase):
volume_id=volume.id)
return
class Utils:
def __init__(self):
self.added_service_offerings = {
'testOffering1' : {'displaytext': 'Test Offering 1', 'cpuspeed': 600, 'cpunumber': 1, 'name': 'Test Offering 1', 'memory': 256},
'testOffering2' : {'displaytext': 'Test Offering 2', 'cpuspeed': 600, 'cpunumber': 2, 'name': 'Test Offering 2', 'memory': 512}
}
class TestChangeServiceOfferingForVmWithSnapshots(cloudstackTestCase):
@classmethod
def setUpClass(cls):
try:
cls._cleanup = []
cls.testClient = super(TestChangeServiceOfferingForVmWithSnapshots, cls).getClsTestClient()
cls.api_client = cls.testClient.getApiClient()
cls.services = cls.testClient.getParsedTestDataConfig()
cls.hypervisor = cls.testClient.getHypervisorInfo()
cls.unsupportedHypervisor = False
if cls.hypervisor.lower() in (KVM.lower(), "hyperv", "lxc"):
cls.unsupportedHypervisor = True
return
cls.domain = get_domain(cls.api_client)
cls.zone = get_zone(
cls.api_client,
cls.testClient.getZoneForTests()
)
cls.services["small"]["zoneid"] = cls.zone.id
cls.template = get_template(
cls.api_client,
cls.zone.id,
cls.services["ostype"]
)
if cls.template == FAILED:
assert False, "get_template() failed to return template\
with description %s" % cls.services["ostype"]
test_offerings = Utils().added_service_offerings
for offering in test_offerings:
cls.services["service_offerings"][offering] = test_offerings[offering]
# Create 2 different service offerings
cls.service_offering_1 = ServiceOffering.create(
cls.api_client,
cls.services["service_offerings"]["testOffering1"]
)
cls._cleanup.append(cls.service_offering_1)
cls.service_offering_2 = ServiceOffering.create(
cls.api_client,
cls.services["service_offerings"]["testOffering2"]
)
cls._cleanup.append(cls.service_offering_2)
cls.account = Account.create(
cls.api_client,
cls.services["account"],
domainid=cls.domain.id
)
cls._cleanup.append(cls.account)
except Exception as e:
cls.tearDownClass()
raise Exception("Warning: Exception in setup : %s" % e)
return
def setUp(self):
self.apiclient = self.testClient.getApiClient()
self.dbclient = self.testClient.getDbConnection()
self.cleanup = []
if self.unsupportedHypervisor:
self.skipTest("Skipping test because unsupported hypervisor\
%s" % self.hypervisor)
def tearDown(self):
# Clean up, terminate the created resources
cleanup_resources(self.apiclient, self.cleanup)
return
@classmethod
def tearDownClass(cls):
try:
cleanup_resources(cls.api_client, cls._cleanup)
except Exception as e:
raise Exception("Warning: Exception during cleanup : %s" % e)
return
def wait_vm_start(self, apiclient, vmid, timeout, sleep):
while timeout:
vms = VirtualMachine.list(apiclient, id=vmid)
vm_list_validation_result = validateList(vms)
if vm_list_validation_result[0] == PASS and vm_list_validation_result[1].state == RUNNING:
return timeout
time.sleep(sleep)
timeout = timeout - 1
return timeout
def checkCPUAndMemory(self, ssh, service_offering):
cpuinfo = ssh.execute("cat /proc/cpuinfo")
cpu_cnt = len([i for i in cpuinfo if "processor" in i])
# 'cpu MHz\t\t: 2660.499'
cpu_speed = [i for i in cpuinfo if "cpu MHz" in i][0].split()[3]
meminfo = ssh.execute("cat /proc/meminfo")
# MemTotal: 1017464 kB
total_mem = [i for i in meminfo if "MemTotal" in i][0].split()[1]
self.debug(
"CPU count: %s, CPU Speed: %s, Mem Info: %s" % (cpu_cnt, cpu_speed, total_mem)
)
self.assertAlmostEqual(
int(cpu_cnt),
service_offering.cpunumber,
"Check CPU Count for service offering"
)
range = 40
if self.hypervisor.lower() == "hyperv":
range = 200
self.assertTrue(
isAlmostEqual(int(int(total_mem) / 1024),
int(service_offering.memory),
range=range
),
"Check Memory(kb) for service offering"
)
@attr(tags=["advanced", "smoke"], required_hardware="true")
def test_change_service_offering_for_vm_with_snapshots(self):
"""Test to change service offering for instances with vm snapshots
"""
# 1) Create Virtual Machine using service offering 1
self.debug("Creating VM using Service Offering 1")
virtual_machine = VirtualMachine.create(
self.apiclient,
self.services["small"],
accountid=self.account.name,
domainid=self.account.domainid,
templateid=self.template.id,
zoneid=self.zone.id,
hypervisor=self.hypervisor,
mode=self.zone.networktype,
serviceofferingid=self.service_offering_1.id
)
# Verify Service OFfering 1 CPU cores and memory
try:
ssh_client = virtual_machine.get_ssh_client(reconnect=True)
self.checkCPUAndMemory(ssh_client, self.service_offering_1)
except Exception as e:
self.fail("SSH failed for virtual machine: %s - %s" % (virtual_machine.ipaddress, e))
# 2) Take VM Snapshot
self.debug("Taking VM Snapshot for VM - ID: %s" % virtual_machine.id)
vm_snapshot = VmSnapshot.create(
self.apiclient,
virtual_machine.id,
)
# 3) Stop Virtual Machine
self.debug("Stopping VM - ID: %s" % virtual_machine.id)
try:
virtual_machine.stop(self.apiclient)
except Exception as e:
self.fail("Failed to stop VM: %s" % e)
# 4) Change service offering for VM with snapshots from Service Offering 1 to Service Offering 2
self.debug("Changing service offering from Service Offering 1 to Service Offering 2 for VM - ID: %s" % virtual_machine.id)
virtual_machine.change_service_offering(self.apiclient, self.service_offering_2.id)
# 5) Start VM
self.debug("Starting VM - ID: %s" % virtual_machine.id)
try:
virtual_machine.start(self.apiclient)
except Exception as e:
self.fail("Failed to start virtual machine: %s, %s" % (virtual_machine.name, e))
# Wait for vm to start
timeout = self.wait_vm_start(self.apiclient, virtual_machine.id, self.services["timeout"],
self.services["sleep"])
if timeout == 0:
self.fail("The virtual machine %s failed to start even after %s minutes"
% (virtual_machine.name, self.services["timeout"]))
list_vm_response = list_virtual_machines(
self.apiclient,
id=virtual_machine.id
)
self.assertEqual(
isinstance(list_vm_response, list),
True,
"Check list response returns a valid list"
)
self.assertNotEqual(
len(list_vm_response),
0,
"Check VM avaliable in List Virtual Machines"
)
self.assertEqual(
list_vm_response[0].state,
"Running",
"Check virtual machine is in running state"
)
self.assertEqual(
list_vm_response[0].id,
virtual_machine.id,
"Check virtual machine id"
)
# 6) Verify service offering has changed
try:
ssh_client_2 = virtual_machine.get_ssh_client(reconnect=True)
self.checkCPUAndMemory(ssh_client_2, self.service_offering_2)
except Exception as e:
self.fail("SSH failed for virtual machine: %s - %s" % (virtual_machine.ipaddress, e))
# 7) Stop Virtual Machine
self.debug("Stopping VM - ID: %s" % virtual_machine.id)
try:
virtual_machine.stop(self.apiclient)
except Exception as e:
self.fail("Failed to stop VM: %s" % e)
# 8) Revert to VM Snapshot
self.debug("Revert to vm snapshot: %s" % vm_snapshot.id)
try:
VmSnapshot.revertToSnapshot(
self.apiclient,
vm_snapshot.id
)
except Exception as e:
self.fail("Failed to revert to VM Snapshot: %s - %s" % (vm_snapshot.id, e))
# 9) Start VM
self.debug("Starting VM - ID: %s" % virtual_machine.id)
try:
virtual_machine.start(self.apiclient)
except Exception as e:
self.fail("Failed to start virtual machine: %s, %s" % (virtual_machine.name, e))
# 10) Verify service offering has changed to Service Offering 1 (from VM Snapshot)
try:
ssh_client_3 = virtual_machine.get_ssh_client(reconnect=True)
self.checkCPUAndMemory(ssh_client_3, self.service_offering_1)
except Exception as e:
self.fail("SSH failed for virtual machine: %s - %s" % (virtual_machine.ipaddress, e))
return