From a34e577d1be5a25d7eb884147212ab51786b55e2 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Fri, 24 May 2013 16:38:55 +0530 Subject: [PATCH] CLOUDSTACK-1453: support restore for VM created from ISO This is to support restore a vm to a new/currently_attached ISO. In the restorevm API we have an optional parameter templateId to restore the vm to the new template/ISO ID. --- .../api/command/user/vm/RestoreVMCmd.java | 5 ++- .../src/com/cloud/vm/UserVmManagerImpl.java | 43 ++++++++++++++----- .../test/com/cloud/vm/UserVmManagerTest.java | 42 ++++++++++++++++-- 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java index 9c33f97c317..2f7d8e10a77 100644 --- a/api/src/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java @@ -35,7 +35,7 @@ import com.cloud.user.Account; import com.cloud.user.UserContext; import com.cloud.uservm.UserVm; -@APICommand(name = "restoreVirtualMachine", description="Restore a VM to original template or new template", responseObject=UserVmResponse.class, since="3.0.0") +@APICommand(name = "restoreVirtualMachine", description="Restore a VM to original template/ISO or new template/ISO", responseObject=UserVmResponse.class, since="3.0.0") public class RestoreVMCmd extends BaseAsyncCmd { public static final Logger s_logger = Logger.getLogger(RestoreVMCmd.class); private static final String s_name = "restorevmresponse"; @@ -44,9 +44,10 @@ public class RestoreVMCmd extends BaseAsyncCmd { required=true, description="Virtual Machine ID") private Long vmId; - @Parameter(name=ApiConstants.TEMPLATE_ID, type=CommandType.UUID, entityType = TemplateResponse.class, description="an optional template Id to restore vm from the new template") + @Parameter(name=ApiConstants.TEMPLATE_ID, type=CommandType.UUID, entityType = TemplateResponse.class, 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; + @Override public String getEventType() { return EventTypes.EVENT_VM_RESTORE; diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index c10a9d8d595..96b95293b47 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -4237,6 +4237,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use long vmId = cmd.getVmId(); Long newTemplateId = cmd.getTemplateId(); + UserVmVO vm = _vmDao.findById(vmId); if (vm == null) { InvalidParameterValueException ex = new InvalidParameterValueException("Cannot find VM with ID " + vmId); @@ -4292,21 +4293,35 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use VolumeVO root = rootVols.get(0); Long templateId = root.getTemplateId(); + boolean isISO = false; if(templateId == null) { - InvalidParameterValueException ex = new InvalidParameterValueException("Currently there is no support to reset a vm that is deployed using ISO " + vm.getUuid()); - ex.addProxyObject(vm, vmId, "vmId"); - throw ex; + // Assuming that for a vm deployed using ISO, template ID is set to NULL + isISO = true; + templateId = vm.getIsoId(); } VMTemplateVO template = null; + //newTemplateId can be either template or ISO id. In the following snippet based on the vm deployment (from template or ISO) it is handled accordingly if(newTemplateId != null) { template = _templateDao.findById(newTemplateId); _accountMgr.checkAccess(caller, null, true, template); + if (isISO) { + if (!template.getFormat().equals(ImageFormat.ISO)) { + throw new InvalidParameterValueException("Invalid ISO id provided to restore the VM "); + } + } else { + if (template.getFormat().equals(ImageFormat.ISO)) { + throw new InvalidParameterValueException("Invalid template id provided to restore the VM "); + } + } } else { + if (isISO && templateId == null) { + throw new CloudRuntimeException("Cannot restore the VM since there is no ISO attached to VM"); + } template = _templateDao.findById(templateId); if (template == null) { InvalidParameterValueException ex = new InvalidParameterValueException( - "Cannot find template for specified volumeid and vmId"); + "Cannot find template/ISO for specified volumeid and vmId"); ex.addProxyObject(vm, vmId, "vmId"); ex.addProxyObject(root, root.getId(), "volumeId"); throw ex; @@ -4325,13 +4340,21 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } } - /* If new template is provided allocate a new volume from new template otherwise allocate new volume from original template */ + /* If new template/ISO is provided allocate a new volume from new template/ISO otherwise allocate new volume from original template/ISO */ VolumeVO newVol = null; - if (newTemplateId != null){ - newVol = volumeMgr.allocateDuplicateVolume(root, newTemplateId); - vm.setGuestOSId(template.getGuestOSId()); - vm.setTemplateId(newTemplateId); - _vmDao.update(vmId, vm); + if (newTemplateId != null) { + if (isISO) { + newVol = volumeMgr.allocateDuplicateVolume(root, null); + vm.setIsoId(newTemplateId); + vm.setGuestOSId(template.getGuestOSId()); + vm.setTemplateId(newTemplateId); + _vmDao.update(vmId, vm); + } else { + newVol = volumeMgr.allocateDuplicateVolume(root, newTemplateId); + vm.setGuestOSId(template.getGuestOSId()); + vm.setTemplateId(newTemplateId); + _vmDao.update(vmId, vm); + } } else { newVol = volumeMgr.allocateDuplicateVolume(root, null); } diff --git a/server/test/com/cloud/vm/UserVmManagerTest.java b/server/test/com/cloud/vm/UserVmManagerTest.java index 6a9711401c9..5eedfa5d815 100755 --- a/server/test/com/cloud/vm/UserVmManagerTest.java +++ b/server/test/com/cloud/vm/UserVmManagerTest.java @@ -24,10 +24,8 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; import java.lang.reflect.Field; import java.util.List; @@ -64,6 +62,7 @@ import com.cloud.storage.VolumeManager; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.Storage.ImageFormat; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountService; @@ -200,6 +199,7 @@ public class UserVmManagerTest { doReturn(false).when(_rootVols).isEmpty(); when(_rootVols.get(eq(0))).thenReturn(_volumeMock); doReturn(3L).when(_volumeMock).getTemplateId(); + doReturn(ImageFormat.VHD).when(_templateMock).getFormat(); when(_templateDao.findById(anyLong())).thenReturn(_templateMock); doNothing().when(_accountMgr).checkAccess(_account, null, true, _templateMock); when(_itMgr.stop(_vmMock, _userMock, _account)).thenReturn(true); @@ -220,6 +220,40 @@ public class UserVmManagerTest { } + // Test restoreVM on providing new ISO Id, when VM(deployed using ISO) is in running state + @Test + public void testRestoreVMF5() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, + ConcurrentOperationException, ResourceAllocationException { + doReturn(VirtualMachine.State.Running).when(_vmMock).getState(); + when(_vmDao.findById(anyLong())).thenReturn(_vmMock); + when(_volsDao.findByInstanceAndType(314L, Volume.Type.ROOT)).thenReturn(_rootVols); + doReturn(false).when(_rootVols).isEmpty(); + when(_rootVols.get(eq(0))).thenReturn(_volumeMock); + doReturn(null).when(_volumeMock).getTemplateId(); + doReturn(3L).when(_vmMock).getIsoId(); + doReturn(ImageFormat.ISO).when(_templateMock).getFormat(); + when(_templateDao.findById(anyLong())).thenReturn(_templateMock); + doNothing().when(_accountMgr).checkAccess(_account, null, true, _templateMock); + when(_itMgr.stop(_vmMock, _userMock, _account)).thenReturn(true); + when(_storageMgr.allocateDuplicateVolume(_volumeMock, null)).thenReturn(_volumeMock); + doNothing().when(_vmMock).setIsoId(14L); + when(_templateMock.getGuestOSId()).thenReturn(5L); + doNothing().when(_vmMock).setGuestOSId(anyLong()); + doNothing().when(_vmMock).setTemplateId(3L); + when(_vmDao.update(314L, _vmMock)).thenReturn(true); + when(_itMgr.start(_vmMock, null, _userMock, _account)).thenReturn(_vmMock); + when(_storageMgr.allocateDuplicateVolume(_volumeMock, null)).thenReturn(_volumeMock); + doNothing().when(_volsDao).attachVolume(anyLong(), anyLong(), anyLong()); + when(_volumeMock.getId()).thenReturn(3L); + doNothing().when(_volsDao).detachVolume(anyLong()); + + when(_templateMock.getUuid()).thenReturn("b1a3626e-72e0-4697-8c7c-a110940cc55d"); + + _userVmMgr.restoreVMInternal(_account, _vmMock, 14L); + + verify(_vmMock, times(1)).setIsoId(14L); + + } // Test scaleVm on incompatible HV. @Test(expected=InvalidParameterValueException.class) public void testScaleVMF1() throws Exception {