mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
The QoS table showed the running VM's bandwidth while an edit changes the definition for the next start. Each row posts average, peak and burst, so an edit wrote the running values back over a pending change, and a direction added while the VM ran had no row. The table shows the persistent definition now. An edit also kept only the posted values and dropped an inbound floor; it now updates them and keeps the rest.
149 lines
6.6 KiB
Python
149 lines
6.6 KiB
Python
"""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 = """<domain><os><type>hvm</type></os><devices>
|
|
<disk type='file' device='disk'><source file='/pool/a.qcow2'/><target dev='vda' bus='virtio'/></disk>
|
|
<disk type='file' device='disk'><source file='/pool/b.qcow2'/><target dev='vdb' bus='virtio'/></disk>
|
|
<interface type='bridge'><mac address='52:54:10:00:00:02'/><source bridge='br0'/></interface>
|
|
<interface type='network'><mac address='52:54:10:00:00:01'/><source network='default'/>
|
|
<bandwidth><inbound average='1' peak='2' burst='3' floor='4'/></bandwidth></interface>
|
|
<video><model type='vga'/></video>
|
|
</devices></domain>"""
|
|
LIVE = """<domain><devices>
|
|
<disk type='file' device='disk'><source file='/pool/a.qcow2'/><target dev='vda' bus='virtio'/><alias name='virtio-disk0'/></disk>
|
|
</devices></domain>"""
|
|
|
|
|
|
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: "<domain><vcpu current='2'>4</vcpu></domain>"
|
|
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: "<domain><memory>131072</memory><currentMemory>131072</currentMemory></domain>"
|
|
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()
|