Skip to content

Commit 7effa61

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 9100fe5 commit 7effa61

1 file changed

Lines changed: 93 additions & 3 deletions

File tree

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

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +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")
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+
)
4047
pool = models.MachinePool(
4148
uuid=sys_uuid.uuid4(), name="test-pool", driver_spec=spec
4249
)
4350
return LibvirtPoolDriver(pool)
4451

4552

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+
4678
def test_domain_console_logs_to_file():
4779
log_path = "/var/log/libvirt/qemu/test-vm.console.log"
4880

@@ -137,3 +169,61 @@ def test_volume_cleanup_still_runs_when_the_domain_is_already_gone(self):
137169
driver.delete_machine(machine, delete_volumes=True)
138170

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

0 commit comments

Comments
 (0)