"""Device edits of a VM: each definition is changed as it describes the device, every NIC type can be a boot device, posted values stay values, and definition changes go through _defineXML, a QoS edit keeps the floor. An active VM's memory is resized by balloon, a stopped one's in its definition. Enabling vCPU hot plug keeps the vCPUs enabled at boot.""" import unittest from unittest.mock import MagicMock from django.conf import settings if not settings.configured: settings.configure(MAC_OUI="52:54:10") from lxml import etree from vrtManager import util from vrtManager.instance import PERSISTENT_XML, wvmInstance from libvirt import VIR_DOMAIN_AFFECT_CONFIG, VIR_DOMAIN_AFFECT_LIVE PERSISTENT = """hvm """ LIVE = """ """ def proxy(active=True): p = wvmInstance.__new__(wvmInstance) # no connection: only these calls p.instance = MagicMock(**{"isActive.return_value": active}) p._XMLDesc = lambda flags: PERSISTENT if flags == PERSISTENT_XML else LIVE p._defineXML = MagicMock() p.wvm = MagicMock() return p def defined(p): (xml,), _ = p._defineXML.call_args return etree.fromstring(xml) class DetachDiskTestCase(unittest.TestCase): def test_each_definition_is_detached_as_it_describes_the_disk(self): p = proxy() p.detach_disk("vda") (live, live_flags), (config, config_flags) = (c.args for c in p.instance.detachDeviceFlags.call_args_list) self.assertEqual((live_flags, config_flags), (VIR_DOMAIN_AFFECT_LIVE, VIR_DOMAIN_AFFECT_CONFIG)) self.assertIn("virtio-disk0", live) self.assertNotIn("alias", config) def test_a_disk_only_in_the_persistent_definition_is_detached_there(self): p = proxy() p.detach_disk("vdb") self.assertEqual([c.args[1] for c in p.instance.detachDeviceFlags.call_args_list], [VIR_DOMAIN_AFFECT_CONFIG]) def test_an_unknown_disk_is_refused(self): p = proxy() with self.assertRaises(util.OperationError): p.detach_disk("vdz") p.instance.detachDeviceFlags.assert_not_called() class VcpuHotplugTestCase(unittest.TestCase): def test_enabling_hotplug_keeps_the_current_vcpus_enabled(self): p = proxy(active=False) p.instance.info.return_value = [5] p._XMLDesc = lambda flags: "4" p.set_vcpu_hotplug(True) self.assertEqual(defined(p).xpath("vcpus/vcpu/@enabled"), ["yes", "yes"]) class DeviceEditsTestCase(unittest.TestCase): def test_a_block_volume_is_attached_by_its_device_path(self): p = proxy(active=False) p._definitions = lambda: [(PERSISTENT_XML, VIR_DOMAIN_AFFECT_CONFIG)] p.attach_disk("vdc", "/dev/vg/c", disk_type="block", target_bus="virtio") xml, _ = p.instance.attachDeviceFlags.call_args.args self.assertEqual(etree.fromstring(xml).find("source").attrib, {"dev": "/dev/vg/c"}) def test_a_bridge_nic_can_be_a_boot_device(self): p = proxy() p.set_bootorder({0: {"type": "network", "dev": "52:54:10:00:00:02"}, 1: {"type": "disk", "dev": "vdb"}}) tree = defined(p) self.assertEqual(tree.xpath("devices/interface[@type='bridge']/boot/@order"), ["1"]) self.assertEqual(tree.xpath("devices/disk[target/@dev='vdb']/boot/@order"), ["2"]) def test_the_video_model_is_a_value(self): p = proxy() p.set_video_model("vga' heads='9") model = defined(p).find("devices/video/model") self.assertEqual(model.attrib, {"type": "vga' heads='9"}) p._defineXML.assert_called_once() def test_qos_changes_go_through_define_xml(self): for change in ( lambda p: p.set_qos("52:54:10:00:00:01", "outbound", 1, 2, 3), lambda p: p.unset_qos("52:54:10:00:00:01", "inbound"), ): p = proxy() change(p) p._defineXML.assert_called_once() p.wvm.defineXML.assert_not_called() def test_qos_is_read_from_either_definition_and_an_edit_keeps_the_floor(self): p = proxy() self.assertEqual(p.get_all_qos(config=True)["52:54:10:00:00:01"][0]["floor"], "4") self.assertEqual(p.get_all_qos(), {}) # the running VM has none p.set_qos("52:54:10:00:00:01", "inbound", 10, 20, 30) inbound = defined(p).find("devices/interface[@type='network']/bandwidth/inbound") self.assertEqual(inbound.attrib, {"average": "10", "peak": "20", "burst": "30", "floor": "4"}) def test_the_default_nic_model_leaves_the_choice_to_libvirt(self): p = proxy() p.change_network("52:54:10:00:00:01", "52:54:10:00:00:01", "default", "net", "default", "") self.assertIsNone(defined(p).find("devices/interface[@type='network']/model")) p = proxy(active=False) p.add_network("52:54:10:00:00:09", "default", model="default") xml, _ = p.instance.attachDeviceFlags.call_args.args self.assertIsNone(etree.fromstring(xml).find("model")) class ResizeMemTestCase(unittest.TestCase): def test_an_active_vm_changes_only_its_current_memory(self): # a paused VM is active too: its guest applies the balloon once resumed p = proxy(active=True) p.resize_mem(96, 160) self.assertEqual( [c.args for c in p.instance.setMemoryFlags.call_args_list], [(96 * 1024, VIR_DOMAIN_AFFECT_LIVE), (96 * 1024, VIR_DOMAIN_AFFECT_CONFIG)], ) p._defineXML.assert_not_called() def test_a_stopped_vm_gets_both_values_in_its_definition(self): p = proxy(active=False) p._XMLDesc = lambda flags: "131072131072" p.resize_mem(96, 160) tree = defined(p) self.assertEqual((tree.findtext("memory"), tree.findtext("currentMemory")), (str(160 * 1024), str(96 * 1024))) p.instance.setMemoryFlags.assert_not_called() if __name__ == "__main__": unittest.main()