diff --git a/nixos/lib/test-driver/src/test_driver/machine/__init__.py b/nixos/lib/test-driver/src/test_driver/machine/__init__.py index f763fa2d12bc..3dc4f1f8d171 100644 --- a/nixos/lib/test-driver/src/test_driver/machine/__init__.py +++ b/nixos/lib/test-driver/src/test_driver/machine/__init__.py @@ -1495,6 +1495,17 @@ class NspawnMachine(BaseMachine): self.logger.info(f"kill NspawnMachine (pid {self.pid})") assert self.process is not None self.process.terminate() + # Wait for the wrapper to finish its context-manager cleanups + # (veth/bridge/netns teardown) before returning, so the driver's + # subsequent vlan teardown does not race against it. + try: + self.process.wait(timeout=30) + except subprocess.TimeoutExpired: + self.logger.error( + f"NspawnMachine {self.name} (pid {self.pid}) did not exit after SIGTERM; sending SIGKILL" + ) + self.process.kill() + self.process.wait() self.process = None def is_up(self) -> bool: diff --git a/nixos/modules/virtualisation/nspawn-container/run-nspawn/src/run_nspawn/__init__.py b/nixos/modules/virtualisation/nspawn-container/run-nspawn/src/run_nspawn/__init__.py index 3eb622cbbfe8..4a28b18d8216 100644 --- a/nixos/modules/virtualisation/nspawn-container/run-nspawn/src/run_nspawn/__init__.py +++ b/nixos/modules/virtualisation/nspawn-container/run-nspawn/src/run_nspawn/__init__.py @@ -101,8 +101,17 @@ def ensure_vlan_bridge(vlan: int) -> typing.Generator[str, None, None]: # releasing this vlan, grab an exclusive lock. with vlan_lock(vlan): if bridge_path.exists(): - child_intf_count = len(list((bridge_path / "brif").iterdir())) - if child_intf_count == 0: + # The VDE tap is owned by the test driver's vde_plug2tap + # and shares its lifetime with the vlan, not with any + # container. Don't count it when deciding whether the + # bridge is still in use, otherwise the bridge would + # never be deleted as long as vde_plug2tap is alive. + child_intfs = [ + p.name + for p in (bridge_path / "brif").iterdir() + if p.name != tap_name + ] + if not child_intfs: logger.info("deleting bridge %s", bridge_name) run_ip("link", "delete", bridge_name)