"""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, and what the edit forms show is read from either definition. A boot order with a missing device changes nothing. 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 = """pending4hvm """ LIVE = """1hvm """ 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_a_vcpu_turned_on_or_off_stays_so_at_the_next_start(self): p = proxy() p.set_vcpu("1", 1) p.instance.setVcpu.assert_called_once_with("1", 1, VIR_DOMAIN_AFFECT_LIVE | VIR_DOMAIN_AFFECT_CONFIG) 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_usb_cdrom_is_a_cd_drive_on_usb_bot(self): # usb-storage, libvirt's choice where QEMU has no usb-bot, shows a hard disk for bus, model in (("usb", "usb-bot"), ("sata", None)): with self.subTest(bus=bus): p = proxy(active=False) p._definitions = lambda: [(PERSISTENT_XML, VIR_DOMAIN_AFFECT_CONFIG)] p.attach_disk("sdb", "/iso/virtio-win.iso", disk_device="cdrom", target_bus=bus, readonly=True) xml, _ = p.instance.attachDeviceFlags.call_args.args self.assertEqual(etree.fromstring(xml).get("model"), model) 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_a_qos_edit_keeps_the_floor(self): p = proxy() 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_a_new_video_model_stays_the_primary_one(self): # the old model's memory sizes are its own: they go p = proxy() p._XMLDesc = lambda flags: PERSISTENT.replace("", "") p.set_video_model("virtio") self.assertEqual(defined(p).find("devices/video/model").attrib, {"type": "virtio", "primary": "yes"}) def test_the_agent_channel_and_vcpu_hot_plug_are_read_from_either_definition(self): p = proxy() pending = ("" "