1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00

fix(interfaces): deleting a bridge keeps the devices it held

With the netcf backend (RHEL/AlmaLinux 8), undefining a bridge also
removes its members' configuration: the device came back without any
after a reboot. Like virsh iface-unbridge, each member is now defined
again on its own after the bridge is undefined, with the bridge's start
mode and, when it is the only member, its addresses. A bond keeps its
mode and devices.

Checked on AlmaLinux 8 (libvirt 8.0, netcf): an ethernet and a bond
member get their ifcfg back, and the bond comes up with the address
after a reboot.
This commit is contained in:
catborise 2026-10-18 21:43:00 +03:00
parent 7526e9ab32
commit 28a73519d7
3 changed files with 53 additions and 0 deletions

View file

@ -576,6 +576,7 @@ A version from before v3.0.0 has no worker: `systemctl disable --now webvirt-wor
- A VM's stats are polled every 5 s like the host graph, the first point as soon as the tab opens; each point is the average since the previous one, disk rates in MB/s (they were MiB/s labelled Mb/s); a counter the host does not report shows a gap instead of 0.
- An ISO uploads to an SSH compute about 3.5 times faster (SFTP writes no longer wait for each 32 KiB); a failed write still fails the upload.
- On a host whose libvirt cannot change network interfaces (the udev backend: RHEL 9 and later, Debian 12), the interface pages offer no create, start, stop or delete; they only failed there.
- Deleting a bridge on a host with the netcf backend (RHEL/AlmaLinux 8) keeps the devices it held: each is defined again on its own with the bridge's start mode and, for a single device, its addresses, as `virsh iface-unbridge` does. They lost their configuration and stayed down after a reboot.
- On a running VM the disk list, its edit form, the QoS table, the title, description, CPU, boot order, video and console fields show the settings as they will be at the next start, so saving an edit no longer reverts another pending change. A boot order that names a device missing from that definition is refused instead of clearing the order. The settings tab of a running VM says so. A QoS edit keeps the inbound floor. Buttons shown as disabled while the VM runs can no longer be submitted with Enter.
- A new disk or CD-ROM on a running VM gets a device name that neither the running VM nor its next-start settings use; it could take the name of a pending disk and stay attached until the next start only.
- The OTP e-mail's QR code is drawn with segno; django-qr-code is no longer required. A `settings.py` kept from an older version still lists `"qr_code"` in `INSTALLED_APPS`: delete that line, or the app does not start in a venv without django-qr-code.

View file

@ -0,0 +1,44 @@
"""Deleting a host interface: netcf removes a bridge's members with the
bridge, so they are defined again on their own (as virsh iface-unbridge)."""
from unittest.mock import MagicMock
from django.test import SimpleTestCase
from lxml import etree
from vrtManager.interface import wvmInterface
ADDRESS = "<protocol family='ipv4'><ip address='192.0.2.10' prefix='24'/></protocol>"
BOND = "<interface type='bond' name='bond0'><bond mode='active-backup'><interface type='ethernet' name='eth1'/></bond></interface>"
def delete(xml):
conn = wvmInterface.__new__(wvmInterface)
conn.iface = MagicMock()
conn.iface.XMLDesc.return_value = xml
conn.wvm = MagicMock()
conn.delete_iface()
conn.iface.undefine.assert_called_once_with()
return [etree.fromstring(call.args[0]) for call in conn.wvm.interfaceDefineXML.call_args_list]
class DeleteInterfaceTestCase(SimpleTestCase):
def test_the_only_member_takes_the_bridge_start_mode_and_addresses(self):
(bond,) = delete(f"<interface type='bridge' name='br1'><start mode='onboot'/>{ADDRESS}<bridge>{BOND}</bridge></interface>")
self.assertEqual(bond.get("name"), "bond0")
self.assertEqual(bond.xpath("bond/@mode"), ["active-backup"])
self.assertEqual(bond.xpath("bond/interface/@name"), ["eth1"])
self.assertEqual(bond.xpath("start/@mode"), ["onboot"])
self.assertEqual(bond.xpath("protocol/ip/@address"), ["192.0.2.10"])
def test_several_members_take_the_start_mode_but_no_address(self):
members = delete(
f"<interface type='bridge' name='br1'><start mode='onboot'/>{ADDRESS}<bridge>"
"<interface type='ethernet' name='eth1'/><interface type='ethernet' name='eth2'/></bridge></interface>"
)
self.assertEqual([m.get("name") for m in members], ["eth1", "eth2"])
for member in members:
self.assertEqual(member.xpath("start/@mode"), ["onboot"])
self.assertEqual(member.xpath("protocol"), [])
def test_an_interface_that_is_not_a_bridge_is_only_undefined(self):
self.assertEqual(delete(f"<interface type='ethernet' name='eth1'><start mode='onboot'/>{ADDRESS}</interface>"), [])

View file

@ -198,4 +198,12 @@ class wvmInterface(wvmConnect):
self.iface.create()
def delete_iface(self):
"""Undefine the interface. netcf removes a bridge's members with it, so
each member is defined again on its own, as virsh iface-unbridge does:
with the bridge's start mode and, when it is the only one, addresses."""
tree = ElementTree.fromstring(self._XMLDesc(VIR_INTERFACE_XML_INACTIVE))
members = tree.findall("bridge/interface")
self.iface.undefine()
for member in members:
member.extend(tree.findall("start") + (tree.findall("protocol") if len(members) == 1 else []))
self.wvm.interfaceDefineXML(ElementTree.tostring(member, encoding="unicode"), 0)