mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
fix(instances): disk and agent channel changes on paused VMs
attach_disk (new or existing volume, CD-ROM), add_guest_agent and remove_guest_agent acted only when the status was running (1) or shut off (5). On a paused VM they changed neither definition and the request reported success. They now use the same rule as the NIC edits: the running definition while the VM is active, paused included, and the persistent one always. remove_guest_agent reads each definition from its own XML, so a channel present only in the persistent one is removed too. Unit tests check paused, running and stopped VMs (paused fails on the previous code). The live effects test adds attach_disk and add_guest_agent, and now also checks that edits meant for the running VM reach it, which showed that an ejected CD-ROM keeps an empty <source> in the live XML; NIC unplug is left to the guest tests, as these VMs have no OS to acknowledge it.
This commit is contained in:
parent
9931b73b2c
commit
16e1a334d0
3 changed files with 69 additions and 22 deletions
|
|
@ -67,6 +67,7 @@ def domain_xml(name, disk, iso):
|
|||
<serial type='pty'><target port='0'/></serial>
|
||||
<rng model='virtio'><backend model='random'>/dev/urandom</backend></rng>
|
||||
<watchdog model='i6300esb' action='reset'/>
|
||||
<controller type='virtio-serial'/> <!-- add_guest_agent hotplugs a channel -->
|
||||
</devices>
|
||||
</domain>"""
|
||||
|
||||
|
|
@ -142,7 +143,7 @@ EDITS = {
|
|||
),
|
||||
"umount_iso": (
|
||||
lambda vm: vm.umount_iso("sda"),
|
||||
lambda t: not t.xpath("devices/disk[target/@dev='sda']/source"),
|
||||
lambda t: not t.xpath("devices/disk[target/@dev='sda']/source/@file"),
|
||||
["devices/disk[target/@dev='sda']/source"], True, True,
|
||||
),
|
||||
"set_link_state": (
|
||||
|
|
@ -166,6 +167,16 @@ EDITS = {
|
|||
lambda t: not t.xpath("devices/interface"),
|
||||
["devices/interface"], True, True,
|
||||
),
|
||||
"attach_disk": (
|
||||
lambda vm: vm.attach_disk("vdc", livetest.create_volume(vm.wvm, P + "fx-extra"), target_bus="virtio", format_type="qcow2"),
|
||||
lambda t: bool(t.xpath("devices/disk[target/@dev='vdc']")),
|
||||
["devices/disk[target/@dev='vdc']"], True, True,
|
||||
),
|
||||
"add_guest_agent": (
|
||||
lambda vm: vm.add_guest_agent(),
|
||||
lambda t: bool(t.xpath("devices/channel/target[@name='org.qemu.guest_agent.0']")),
|
||||
["devices/channel[target/@name='org.qemu.guest_agent.0']"], True, True,
|
||||
),
|
||||
"edit_disk (cache mode)": (
|
||||
lambda vm: vm.edit_disk("vda", vm.get_disk_devices(config=True)[0]["path"], False, False, "virtio",
|
||||
"keep-serial", "qcow2", "writeback", None, None, None),
|
||||
|
|
@ -246,6 +257,10 @@ class LiveXmlEffectsTestCase(SimpleTestCase):
|
|||
after = self.persistent(dom)
|
||||
self.assertTrue(effect(after), "edit had no effect")
|
||||
self.assertEqual(canonical(after, touched), canonical(before, touched), "unrelated change")
|
||||
# An unplug completes only when the guest acknowledges it; these
|
||||
# VMs have no OS (test_live_guest covers it with a real guest).
|
||||
if running and changes_live and name != "delete_network":
|
||||
self.assertTrue(effect(self.live(dom)), "edit did not reach the running VM")
|
||||
if running and not changes_live:
|
||||
self.assertEqual(
|
||||
canonical(self.live(dom), ["devices/graphics/@port"]),
|
||||
|
|
|
|||
|
|
@ -831,11 +831,8 @@ class wvmInstance(wvmConnect):
|
|||
if serial is not None and serial != "None" and serial != "":
|
||||
xml_disk += f"""<serial>{serial}</serial>"""
|
||||
xml_disk += """</disk>"""
|
||||
if self.get_status() == 1:
|
||||
self.instance.attachDeviceFlags(xml_disk, VIR_DOMAIN_AFFECT_LIVE)
|
||||
self.instance.attachDeviceFlags(xml_disk, VIR_DOMAIN_AFFECT_CONFIG)
|
||||
if self.get_status() == 5:
|
||||
self.instance.attachDeviceFlags(xml_disk, VIR_DOMAIN_AFFECT_CONFIG)
|
||||
for _, affect in self._definitions():
|
||||
self.instance.attachDeviceFlags(xml_disk, affect)
|
||||
|
||||
def detach_disk(self, target_dev):
|
||||
tree = etree.fromstring(self._XMLDesc(0))
|
||||
|
|
@ -1927,24 +1924,17 @@ class wvmInstance(wvmConnect):
|
|||
<target type='virtio' name='org.qemu.guest_agent.0'/>
|
||||
</channel>
|
||||
"""
|
||||
if self.get_status() == 1:
|
||||
self.instance.attachDeviceFlags(channel_xml, VIR_DOMAIN_AFFECT_LIVE)
|
||||
self.instance.attachDeviceFlags(channel_xml, VIR_DOMAIN_AFFECT_CONFIG)
|
||||
if self.get_status() == 5:
|
||||
self.instance.attachDeviceFlags(channel_xml, VIR_DOMAIN_AFFECT_CONFIG)
|
||||
for _, affect in self._definitions():
|
||||
self.instance.attachDeviceFlags(channel_xml, affect)
|
||||
|
||||
def remove_guest_agent(self):
|
||||
tree = etree.fromstring(self._XMLDesc(0))
|
||||
for target in tree.xpath(
|
||||
"/domain/devices/channel[@type='unix']/target[@name='org.qemu.guest_agent.0']"
|
||||
):
|
||||
parent = target.getparent()
|
||||
channel_xml = etree.tostring(parent).decode()
|
||||
if self.get_status() == 1:
|
||||
self.instance.detachDeviceFlags(channel_xml, VIR_DOMAIN_AFFECT_LIVE)
|
||||
self.instance.detachDeviceFlags(channel_xml, VIR_DOMAIN_AFFECT_CONFIG)
|
||||
if self.get_status() == 5:
|
||||
self.instance.detachDeviceFlags(channel_xml, VIR_DOMAIN_AFFECT_CONFIG)
|
||||
for xml_flags, affect in self._definitions():
|
||||
tree = etree.fromstring(self._XMLDesc(xml_flags))
|
||||
for target in tree.xpath(
|
||||
"/domain/devices/channel[@type='unix']/target[@name='org.qemu.guest_agent.0']"
|
||||
):
|
||||
channel_xml = etree.tostring(target.getparent()).decode()
|
||||
self.instance.detachDeviceFlags(channel_xml, affect)
|
||||
|
||||
def get_guest_agent(self):
|
||||
def _get_agent(doc):
|
||||
|
|
|
|||
|
|
@ -78,5 +78,47 @@ class XmlFidelityTestCase(unittest.TestCase):
|
|||
)
|
||||
|
||||
|
||||
AGENT_DOMAIN = """<domain><name>vm</name><devices><channel type='unix'>
|
||||
<target type='virtio' name='org.qemu.guest_agent.0'/></channel></devices></domain>"""
|
||||
|
||||
|
||||
class DeviceEditsByStateTestCase(unittest.TestCase):
|
||||
"""Disk and agent channel changes reach the running definition whenever the
|
||||
VM is active, paused included, and the persistent one always."""
|
||||
|
||||
EDITS = {
|
||||
"attach_disk": ("attachDeviceFlags", lambda vm: vm.attach_disk("vdb", "/var/lib/libvirt/images/d.qcow2")),
|
||||
"add_guest_agent": ("attachDeviceFlags", lambda vm: vm.add_guest_agent()),
|
||||
"remove_guest_agent": ("detachDeviceFlags", lambda vm: vm.remove_guest_agent()),
|
||||
}
|
||||
|
||||
def affected(self, state, active):
|
||||
from libvirt import VIR_DOMAIN_AFFECT_CONFIG, VIR_DOMAIN_AFFECT_LIVE
|
||||
|
||||
names = {VIR_DOMAIN_AFFECT_LIVE: "live", VIR_DOMAIN_AFFECT_CONFIG: "config"}
|
||||
result = {}
|
||||
for name, (method, edit) in self.EDITS.items():
|
||||
vm = wvmInstance.__new__(wvmInstance)
|
||||
vm.instance = MagicMock()
|
||||
vm.instance.isActive.return_value = active
|
||||
vm.get_status = MagicMock(return_value=state)
|
||||
vm._XMLDesc = MagicMock(return_value=AGENT_DOMAIN)
|
||||
edit(vm)
|
||||
result[name] = [names[c.args[1]] for c in getattr(vm.instance, method).call_args_list]
|
||||
return result
|
||||
|
||||
def test_paused_vm(self):
|
||||
for name, affected in self.affected(state=3, active=True).items():
|
||||
self.assertEqual(affected, ["live", "config"], name)
|
||||
|
||||
def test_running_vm(self):
|
||||
for name, affected in self.affected(state=1, active=True).items():
|
||||
self.assertEqual(affected, ["live", "config"], name)
|
||||
|
||||
def test_stopped_vm(self):
|
||||
for name, affected in self.affected(state=5, active=False).items():
|
||||
self.assertEqual(affected, ["config"], name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue