diff --git a/server/src/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/com/cloud/storage/VolumeApiServiceImpl.java index 5a53f9c531d..6a59f548669 100644 --- a/server/src/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/com/cloud/storage/VolumeApiServiceImpl.java @@ -1744,8 +1744,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } private void validateRootVolumeDetachAttach(VolumeVO volume, UserVmVO vm) { - if (!(vm.getHypervisorType() == HypervisorType.XenServer || vm.getHypervisorType() == HypervisorType.VMware)) { - throw new InvalidParameterValueException("Root volume detach is allowed for hypervisor type " + HypervisorType.XenServer + " only"); + if (!(vm.getHypervisorType() == HypervisorType.XenServer || vm.getHypervisorType() == HypervisorType.VMware || vm.getHypervisorType() == HypervisorType.KVM || vm.getHypervisorType() == HypervisorType.Simulator)) { + throw new InvalidParameterValueException("Root volume detach is not supported for hypervisor type " + vm.getHypervisorType() ); } if (!(vm.getState() == State.Stopped) || (vm.getState() == State.Destroyed)) { throw new InvalidParameterValueException("Root volume detach can happen only when vm is in states: " + State.Stopped.toString() + " or " + State.Destroyed.toString()); diff --git a/test/integration/component/test_volumes.py b/test/integration/component/test_volumes.py index 12c8f9de347..941e225f54f 100644 --- a/test/integration/component/test_volumes.py +++ b/test/integration/component/test_volumes.py @@ -603,7 +603,134 @@ class TestAttachDetachVolume(cloudstackTestCase): "Check the state of VM" ) except Exception as e: - self.fail("Exception occuered: %s" % e) + self.fail("Exception occurred: %s" % e) + return + + @attr(tags=["advanced", "advancedns"]) + def test_02_root_volume_attach_detach(self): + """Test Root Volume attach/detach to VM + """ + + # Validate the following + # 1. Deploy a VM + # 2. Check for root volume + # 3. Stop VM + # 4. Detach root volume + # 5. Verify root volume detached + # 6. Attach root volume + # 7. Start VM + + try: + # Check for root volume + root_volume_response = Volume.list( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + type='ROOT', + listall=True + ) + self.assertNotEqual( + root_volume_response, + None, + "Check if root volume exists in ListVolumes" + ) + self.assertEqual( + isinstance(root_volume_response, list), + True, + "Check list volumes response for valid list" + ) + # Grab the root volume for later use + root_volume = root_volume_response[0] + + # Stop VM + self.debug("Stopping the VM: %s" % self.virtual_machine.id) + self.virtual_machine.stop(self.apiclient) + + # Ensure VM is stopped before detaching the root volume + time.sleep(self.services["sleep"]) + + vm_response = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine.id, + ) + vm = vm_response[0] + self.assertEqual( + vm.state, + 'Stopped', + "Check the state of VM" + ) + + # Detach root volume from VM + self.virtual_machine.detach_volume( + self.apiclient, + root_volume + ) + + # Verify that root disk is gone + no_root_volume_response = Volume.list( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + type='ROOT', + listall=True + ) + self.assertEqual( + no_root_volume_response, + None, + "Check if root volume exists in ListVolumes" + ) + + # Attach root volume to VM + self.virtual_machine.attach_volume( + self.apiclient, + root_volume, + 0 + ) + + # Check for root volume + new_root_volume_response = Volume.list( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + type='ROOT', + listall=True + ) + self.assertNotEqual( + new_root_volume_response, + None, + "Check if root volume exists in ListVolumes" + ) + self.assertEqual( + isinstance(new_root_volume_response, list), + True, + "Check list volumes response for valid list" + ) + + # Start VM + self.virtual_machine.start(self.apiclient) + # Sleep to ensure that VM is in ready state + time.sleep(self.services["sleep"]) + + vm_response = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine.id, + ) + # Verify VM response to check whether VM deployment was successful + self.assertEqual( + isinstance(vm_response, list), + True, + "Check list VM response for valid list" + ) + self.assertNotEqual( + len(vm_response), + 0, + "Check VMs available in List VMs response" + ) + vm = vm_response[0] + self.assertEqual( + vm.state, + 'Running', + "Check the state of VM" + ) + except Exception as e: + self.fail("Exception occurred: %s" % e) return diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 4e04ba82496..5d81e5fe8e5 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -667,11 +667,15 @@ class VirtualMachine: }) apiclient.migrateVirtualMachineWithVolume(cmd) - def attach_volume(self, apiclient, volume): + def attach_volume(self, apiclient, volume, deviceid=None): """Attach volume to instance""" cmd = attachVolume.attachVolumeCmd() cmd.id = volume.id cmd.virtualmachineid = self.id + + if deviceid is not None: + cmd.deviceid = deviceid + return apiclient.attachVolume(cmd) def detach_volume(self, apiclient, volume):