1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 12:46:49 +00:00

fix(instances): a network device keeps its source unless another is chosen

The Edit dialog of a VM's network device selected the listed network
or interface whose name matched the device's source, and the first one
when none did: saving only a new model moved a macvtap device or a
bridge the host's lists did not show onto the first network, a bridge
named like a network became that network, and a macvtap device chosen
again lost its mode.

The dialog now offers the device's own source first, "<name>, as it
is", and posts nothing for it; the view passes no source and the
device's source element is left as it is. Choosing another source
changes it as before.
This commit is contained in:
catborise 2026-10-31 17:05:20 +03:00
parent 3d69dfe5d9
commit 2e17661390
6 changed files with 44 additions and 15 deletions

View file

@ -2706,7 +2706,8 @@ class wvmInstance(wvmConnect):
"""Edit the NIC with old_mac in the persistent definition, in place.
Settings this form does not edit (QoS, link state, driver, PCI address)
stay. A running VM gets the change at its next start.
stay, and so does the source when source is None. A running VM gets
the change at its next start.
"""
tree = etree.fromstring(self._XMLDesc(PERSISTENT_XML))
found = tree.xpath("./devices/interface[mac/@address=$mac]", mac=old_mac)
@ -2716,14 +2717,15 @@ class wvmInstance(wvmConnect):
if iface.get("type") == "hostdev":
raise util.OperationError("A network interface that is a host device is changed in the XML editor")
iface_type = {"net": "network", "bridge": "bridge"}.get(source_type, "direct")
iface.set("type", iface_type)
for old_source in iface.findall("source"):
iface.remove(old_source)
source_attrs = {"network": {"network": source}, "bridge": {"bridge": source}}.get(
iface_type, {"dev": source, "mode": "bridge"}
)
iface.insert(1, etree.Element("source", **source_attrs))
if source is not None:
iface_type = {"net": "network", "bridge": "bridge"}.get(source_type, "direct")
iface.set("type", iface_type)
for old_source in iface.findall("source"):
iface.remove(old_source)
source_attrs = {"network": {"network": source}, "bridge": {"bridge": source}}.get(
iface_type, {"dev": source, "mode": "bridge"}
)
iface.insert(1, etree.Element("source", **source_attrs))
iface.find("mac").set("address", mac)
for old_model in iface.findall("model"):

View file

@ -14,6 +14,7 @@ DOMAIN_XML = """<domain><devices>
<interface type='network'><mac address='52:54:00:00:00:02'/><source network='default'/><model type='virtio'/>
<bandwidth><outbound average='1000'/></bandwidth><link state='down'/>
<address type='pci' domain='0x0000' bus='0x02' slot='0x00' function='0x0'/></interface>
<interface type='direct'><mac address='52:54:00:00:00:04'/><source dev='eth0' mode='vepa'/><model type='virtio'/></interface>
</devices></domain>"""
@ -34,7 +35,7 @@ class TestChangeNetwork(unittest.TestCase):
inst.change_network("52:54:00:00:00:02", "52:54:00:00:00:03", "br0", "bridge", "e1000", "clean-traffic")
defined = etree.fromstring(inst.wvm.defineXML.call_args[0][0])
first, second = defined.findall("devices/interface")
first, second, _direct = defined.findall("devices/interface")
self.assertEqual(first.find("mac").get("address"), "52:54:00:00:00:01")
self.assertEqual(second.get("type"), "bridge")
self.assertEqual(second.find("mac").get("address"), "52:54:00:00:00:03")
@ -46,6 +47,18 @@ class TestChangeNetwork(unittest.TestCase):
self.assertEqual(second.find("link").get("state"), "down")
self.assertEqual(second.find("address").get("bus"), "0x02")
def defined_nic(self, *args):
from lxml import etree
inst = stopped_instance()
inst.change_network(*args)
return etree.fromstring(inst.wvm.defineXML.call_args[0][0]).findall("devices/interface")[2]
def test_no_source_keeps_the_one_the_nic_has(self):
nic = self.defined_nic("52:54:00:00:00:04", "52:54:00:00:00:04", None, None, "e1000", "")
self.assertEqual((nic.get("type"), nic.find("source").attrib), ("direct", {"dev": "eth0", "mode": "vepa"}))
self.assertEqual(nic.find("model").get("type"), "e1000")
def test_unknown_old_mac_changes_nothing(self):
inst = stopped_instance()
with self.assertRaises(libvirtError):