Skip to content

Commit be12a82

Browse files
test(drivers): cover boot vs real port interface-type selection
Locks in the create_machine()/attach_port() behavior fixed in the previous commit: the boot-network port always gets type='network' on a bridge-type hypervisor, while any other port (attach_port(), and create_machine()'s post-flash recreate_machine() path) keeps honoring network_type - matching what real bridge-type hypervisors actually run (raw bridge-type interfaces), per PR #527 review feedback.
1 parent a6c637b commit be12a82

1 file changed

Lines changed: 96 additions & 4 deletions

File tree

exordos_core/tests/unit/compute/pool/drivers/test_libvirt.py

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,55 @@
2626
# collection when they're not available.
2727
pytest.importorskip("libvirt")
2828

29+
from exordos_core.compute import constants as nc # noqa: E402
2930
from exordos_core.compute.dm import models # noqa: E402
3031
from exordos_core.compute.pool.drivers.libvirt import LibvirtPoolDriver # noqa: E402
3132
from exordos_core.compute.pool.drivers.libvirt import XMLLibvirtInstance # noqa: E402
3233
from exordos_core.compute.pool.drivers.libvirt import domain_template # noqa: E402
3334

3435

35-
def _local_driver() -> LibvirtPoolDriver:
36+
def _local_driver(network_type: str = "network") -> LibvirtPoolDriver:
3637
# libvirt's built-in "test" driver simulates a hypervisor in-memory -
3738
# no real virtualization or daemon needed, so real libvirt calls
38-
# (lookupByUUIDString, etc.) can be exercised end-to-end.
39-
spec = models.LibvirtPoolDriverSpec(connection_uri="test:///default")
40-
pool = models.MachinePool(uuid=sys_uuid.uuid4(), name="test-pool", driver_spec=spec)
39+
# (lookupByUUIDString, etc.) can be exercised end-to-end. It even ships
40+
# a default storage pool ("default-pool"), so create_machine() can be
41+
# exercised end-to-end too.
42+
spec = models.LibvirtPoolDriverSpec(
43+
connection_uri="test:///default",
44+
network_type=network_type,
45+
storage_pool="default-pool",
46+
)
47+
pool = models.MachinePool(
48+
uuid=sys_uuid.uuid4(), name="test-pool", driver_spec=spec
49+
)
4150
return LibvirtPoolDriver(pool)
4251

4352

53+
def _machine() -> models.Machine:
54+
return models.Machine(
55+
uuid=sys_uuid.uuid4(),
56+
project_id=sys_uuid.uuid4(),
57+
name="test-vm",
58+
cores=1,
59+
ram=512,
60+
)
61+
62+
63+
def _port(uuid: sys_uuid.UUID, source: str) -> models.Port:
64+
return models.Port(
65+
uuid=uuid,
66+
project_id=sys_uuid.uuid4(),
67+
mac=models.Port.generate_mac(),
68+
source=source,
69+
status=nc.PortStatus.ACTIVE.value,
70+
)
71+
72+
73+
def _live_interface(driver: LibvirtPoolDriver, machine: models.Machine) -> ET.Element:
74+
domain = driver._client.lookupByUUIDString(str(machine.uuid))
75+
return ET.fromstring(domain.XMLDesc()).find(".//devices/interface")
76+
77+
4478
def test_domain_console_logs_to_file():
4579
log_path = "/var/log/libvirt/qemu/test-vm.console.log"
4680

@@ -133,3 +167,61 @@ def test_volume_cleanup_still_runs_when_the_domain_is_already_gone(self):
133167
driver.delete_machine(machine, delete_volumes=True)
134168

135169
mock_list_volumes.assert_called_once_with(machine)
170+
171+
172+
class TestCreateMachine:
173+
def test_boot_port_always_uses_network_type_on_a_bridge_hypervisor(self):
174+
# Regression: a bridge-type hypervisor must not have the boot
175+
# network's (logical, potentially long) name treated as a literal
176+
# host bridge device name - libvirt rejects that outright as too
177+
# long for IFNAMSIZ.
178+
driver = _local_driver(network_type="bridge")
179+
machine = _machine()
180+
port = _port(nc.BOOT_NETWORK_PORT_UUID, source="exordos-core-boot-net")
181+
182+
driver.create_machine(machine, volumes=[], ports=[port])
183+
184+
interface = _live_interface(driver, machine)
185+
assert interface.get("type") == "network"
186+
assert interface.find("source").get("network") == "exordos-core-boot-net"
187+
188+
def test_real_port_honors_the_hypervisors_network_type(self):
189+
# Regression: on a real bridge-type hypervisor, ports are raw
190+
# bridge-type interfaces (source=<bridge device>) - not a libvirt
191+
# network wrapping one. create_machine() is also used by
192+
# recreate_machine() to rebuild the domain with the real port(s)
193+
# post-flash, so it must not force type='network' on those.
194+
driver = _local_driver(network_type="bridge")
195+
machine = _machine()
196+
port = _port(sys_uuid.uuid4(), source="br0")
197+
198+
driver.create_machine(machine, volumes=[], ports=[port])
199+
200+
interface = _live_interface(driver, machine)
201+
assert interface.get("type") == "bridge"
202+
assert interface.find("source").get("bridge") == "br0"
203+
204+
205+
class TestAttachPort:
206+
def test_honors_the_hypervisors_network_type_for_bridge_hypervisors(self):
207+
# Regression: attach_port() is only ever used for the real port,
208+
# which on a bridge-type hypervisor is a raw bridge-type interface
209+
# (source=<bridge device>) - not a libvirt network wrapping one.
210+
#
211+
# libvirt's test:// driver doesn't support attachDeviceFlags()'s
212+
# LIVE+CONFIG flag combination, so mock the domain lookup instead
213+
# of exercising a real domain end-to-end.
214+
driver = _local_driver(network_type="bridge")
215+
machine = _machine()
216+
port = _port(sys_uuid.uuid4(), source="br0")
217+
218+
mock_domain = mock.MagicMock()
219+
with mock.patch.object(
220+
driver._client, "lookupByUUIDString", return_value=mock_domain
221+
):
222+
driver.attach_port(machine, port)
223+
224+
interface_xml, _flags = mock_domain.attachDeviceFlags.call_args[0]
225+
interface = ET.fromstring(interface_xml)
226+
assert interface.get("type") == "bridge"
227+
assert interface.find("source").get("bridge") == "br0"

0 commit comments

Comments
 (0)