diff --git a/interfaces/test_forms.py b/interfaces/test_forms.py index cbf77e98..0c42a236 100644 --- a/interfaces/test_forms.py +++ b/interfaces/test_forms.py @@ -2,6 +2,8 @@ from unittest.mock import MagicMock, patch +import libvirt + from computes.models import Compute from django.contrib.auth import get_user_model from django.test import SimpleTestCase, TestCase @@ -64,6 +66,7 @@ class Ipv6XmlTestCase(SimpleTestCase): conn = wvmInterfaces.__new__(wvmInterfaces) conn.define_iface = MagicMock() conn.get_iface = MagicMock() + conn.get_iface.return_value.XMLDesc.return_value = "" conn.create_iface("br1", "bridge", "onboot", "eth1", "none", "", "", "static", "2001:db8::10/64", gw, "on", 0) return etree.fromstring(conn.define_iface.call_args[0][0]) @@ -147,6 +150,7 @@ class Ipv4XmlTestCase(SimpleTestCase): conn = wvmInterfaces.__new__(wvmInterfaces) conn.define_iface = MagicMock() conn.get_iface = MagicMock() + conn.get_iface.return_value.XMLDesc.return_value = "" conn.create_iface("br1", "bridge", "onboot", "eth1", "static", addr, gw, "none", "", "", "on", 0) return etree.fromstring(conn.define_iface.call_args[0][0]) @@ -179,6 +183,78 @@ class HostDeviceTestCase(SimpleTestCase): self.assertIn("name", f.errors) +def no_interface(name): + err = libvirt.libvirtError("Interface not found: no interface with matching name '%s'" % name) + err.err = (libvirt.VIR_ERR_NO_INTERFACE, libvirt.VIR_FROM_INTERFACE, str(err), 2, name, None, None, 0, 0) + return err + + +class BridgeMemberXmlTestCase(SimpleTestCase): + """A bridge member is declared as the host defines it: a bond keeps its + mode and devices, a vlan its tag and device.""" + + def member(self, host_xml): + conn = wvmInterfaces.__new__(wvmInterfaces) + conn.define_iface = MagicMock() + created = MagicMock() + + def get_iface(name): + if name == "br1": + return created + if host_xml is None: + raise no_interface(name) + iface = MagicMock() + iface.XMLDesc.return_value = host_xml + return iface + + conn.get_iface = MagicMock(side_effect=get_iface) + conn.create_iface("br1", "bridge", "onboot", "bond0", "none", "", "", "none", "", "", "on", 0) + self.assertTrue(created.create.called) + members = etree.fromstring(conn.define_iface.call_args[0][0]).findall("bridge/interface") + self.assertEqual(len(members), 1) + return members[0] + + def test_a_bond_keeps_its_mode_and_devices(self): + member = self.member( + "" + "" + "" + "" + "" + ) + self.assertEqual((member.get("type"), member.get("name")), ("bond", "bond0")) + self.assertEqual(member.find("bond").get("mode"), "active-backup") + self.assertEqual([i.get("name") for i in member.findall("bond/interface")], ["eth0", "eth1"]) + self.assertEqual(member.find("bond/miimon").get("freq"), "100") + # what only a top-level interface has: the bridge holds the addresses + for tag in ("start", "mtu", "protocol"): + self.assertIsNone(member.find(tag), tag) + + def test_a_vlan_keeps_its_tag_and_device(self): + member = self.member( + "" + "" + ) + self.assertEqual(member.get("type"), "vlan") + self.assertEqual(member.find("vlan").get("tag"), "42") + self.assertEqual(member.find("vlan/interface").get("name"), "eth0") + self.assertIsNone(member.find("start")) + + def test_a_device_the_host_does_not_define_is_ethernet(self): + member = self.member(None) + self.assertEqual(member.attrib, {"type": "ethernet", "name": "bond0"}) + + def test_other_lookup_errors_are_not_hidden(self): + conn = wvmInterfaces.__new__(wvmInterfaces) + conn.define_iface = MagicMock() + err = libvirt.libvirtError("cannot recv data") + err.err = (libvirt.VIR_ERR_SYSTEM_ERROR, libvirt.VIR_FROM_RPC, str(err), 2, None, None, None, 0, 0) + conn.get_iface = MagicMock(side_effect=err) + with self.assertRaises(libvirt.libvirtError): + conn.create_iface("br1", "bridge", "onboot", "bond0", "none", "", "", "none", "", "", "on", 0) + self.assertFalse(conn.define_iface.called) + + class BridgeSettingsTestCase(SimpleTestCase): def test_a_bridge_needs_stp_and_a_delay(self): for field in ("stp", "delay"): diff --git a/vrtManager/interface.py b/vrtManager/interface.py index 9a8830c0..333d2fa7 100644 --- a/vrtManager/interface.py +++ b/vrtManager/interface.py @@ -1,6 +1,6 @@ from xml.etree import ElementTree -from libvirt import VIR_INTERFACE_XML_INACTIVE +from libvirt import VIR_ERR_NO_INTERFACE, VIR_INTERFACE_XML_INACTIVE, libvirtError from vrtManager import util from vrtManager.connection import wvmConnect @@ -10,6 +10,21 @@ class wvmInterfaces(wvmConnect): def define_iface(self, xml, flag=0): self.wvm.interfaceDefineXML(xml, flag) + def bridge_member_xml(self, netdev): + """netdev as a bridge member: as the host defines it (a bond keeps its + mode and devices, a vlan its tag), without what only a top-level + interface has. A device the host does not define is ethernet.""" + try: + member = ElementTree.fromstring(self.get_iface(netdev).XMLDesc(VIR_INTERFACE_XML_INACTIVE)) + except libvirtError as err: + if err.get_error_code() != VIR_ERR_NO_INTERFACE: + raise + return f"" + for tag in ("start", "mtu", "protocol"): + for child in member.findall(tag): + member.remove(child) + return ElementTree.tostring(member, encoding="unicode") + def create_iface( self, name, @@ -51,7 +66,7 @@ class wvmInterfaces(wvmConnect): xml += """""" if itype == "bridge": xml += f""" - + {self.bridge_member_xml(netdev)} """ xml += """""" self.define_iface(xml)