diff --git a/server/src/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/com/cloud/storage/VolumeApiServiceImpl.java index 742450ab566..2ea9dbbeed6 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..77d8679dd69 100644 --- a/test/integration/component/test_volumes.py +++ b/test/integration/component/test_volumes.py @@ -603,7 +603,140 @@ 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"], required_hardware="false") + def test_02_root_volume_attach_detach(self): + """Test Root Volume attach/detach to VM + """ + + # Validate the following + # 1. Deploy a VM + # 2. Verify that we are testing a supported hypervisor + # 3. Check for root volume + # 4. Stop VM + # 5. Detach root volume + # 6. Verify root volume detached + # 7. Attach root volume + # 8. Start VM + + # Verify we are using a supported hypervisor + if (self.hypervisor.lower() == 'vmware' + or self.hypervisor.lower() == 'kvm' + or self.hypervisor.lower() == 'simulator' + or self.hypervisor.lower() == 'xenserver'): + + try: + # Check for root volume + root_volume_response = Volume.list( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + type='ROOT', + listall=True + ) + + self.assertEqual( + validateList(root_volume_response)[0], + PASS, + "Invalid response returned for root volume 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) + + vm_response = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine.id, + ) + + # Ensure that vm_response is a valid list + self.assertEqual( + validateList(vm_response)[0], + PASS, + "Invalid response returned for vm_response list" + ) + + 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 + ) + + # Ensure that new_root_volume_response is a valid list + self.assertEqual( + validateList(new_root_volume_response)[0], + PASS, + "Invalid response returned for new_root_volume_response list" + ) + + # Start VM + self.virtual_machine.start(self.apiclient) + + vm_response = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine.id, + ) + + # Verify VM response to check whether VM deployment was successful + self.assertEqual( + validateList(vm_response)[0], + PASS, + "Invalid response returned for vm_response list during VM start up" + ) + + vm = vm_response[0] + self.assertEqual( + vm.state, + 'Running', + "Ensure the state of VM is running" + ) + + except Exception as e: + self.fail("Exception occurred: %s" % e) + + else: + self.skipTest("Root Volume attach/detach is not supported on %s " % self.hypervisor) return diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index f9a03440d96..906fb57695d 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):