From d6e80990b292921e949cc8253dda1568d579e60f Mon Sep 17 00:00:00 2001 From: sjat Date: Fri, 19 Jun 2026 22:41:11 +0200 Subject: [PATCH] fix(integration): real wait_for_ip arp-fallback test + document substrate coverage gap Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/FRICTION.md | 12 ++++++++++ .../molecule/default/converge.yml | 7 ++++++ tests/test_integration_vm.py | 24 +++++++++++++++++-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/FRICTION.md b/docs/FRICTION.md index 1067556..3dd723b 100644 --- a/docs/FRICTION.md +++ b/docs/FRICTION.md @@ -264,6 +264,18 @@ harness on ubongo and shaking it down against real KVM (spec/plan in docs/superp and virsh domain names keep the original underscore). Sanitization rule: RFC-952 hostnames allow hyphens, not underscores. +- `[friction]` **Molecule Docker image can't `apt install` → roles with real package tasks + have no Molecule substrate coverage** (2026-06-19): the Docker Molecule image ships with + cleared apt-lists and no internet access, so any role whose core work is `apt install` — + `base`, `docker_host`, `integration_test` — cannot cover its package/substrate tasks in + Molecule. Those tasks are validated only by `make test-integration` (ADR-025, real KVM). + The gap is systemic: it affects every role with non-trivial package or system-level setup. + → systematization idea: provide a Molecule image or driver that can install packages (e.g. + a custom Docker image with pre-seeded apt-lists, or a `prepare.yml` that pre-installs + packages from a local cache), or an alternative driver (e.g. `molecule-libvirt` using the + same KVM harness), so substrate tasks get real Molecule unit coverage rather than relying + entirely on the integration harness. + --- ## Kaizen reviews — decisions ledger diff --git a/roles/integration_test/molecule/default/converge.yml b/roles/integration_test/molecule/default/converge.yml index 89fb934..4b0f895 100644 --- a/roles/integration_test/molecule/default/converge.yml +++ b/roles/integration_test/molecule/default/converge.yml @@ -3,6 +3,13 @@ # (no internet; KVM unusable in a container). This converge exercises only the # nftables drop-in rendering via tasks_from, which IS meaningful in a container. # The full role (packages/libvirt) is exercised by make test-integration. +# +# Coverage split: +# Docker Molecule (this file): nftables drop-in rendering only. +# make test-integration (ADR-025, real KVM): libvirt/KVM package install, cache +# dir creation, and end-to-end VM lifecycle — the role's substrate tasks. +# The Docker scenario intentionally covers only the firewall drop-in; substrate +# coverage lives in the real-KVM integration harness, not here. - name: Converge hosts: all become: true diff --git a/tests/test_integration_vm.py b/tests/test_integration_vm.py index 7061c0c..8d4fe38 100644 --- a/tests/test_integration_vm.py +++ b/tests/test_integration_vm.py @@ -1,5 +1,6 @@ import importlib.util import pathlib +import types import pytest _PATH = pathlib.Path(__file__).resolve().parent.parent / "scripts" / "integration-vm.py" @@ -32,15 +33,34 @@ def test_parse_lease_ip_extracts_ipv4(): def test_parse_lease_ip_none_when_absent(): assert ivm.parse_lease_ip("no leases\n") is None -def test_parse_lease_ip_arp_source(): +def test_parse_lease_ip_format_is_source_agnostic(): # virsh domifaddr --source arp output format is identical to --source lease; - # this test proves parse_lease_ip handles it so the arp fallback in wait_for_ip works. + # this test only proves the regex is format-agnostic (both sources produce the + # same table). The behavioral arp-fallback in wait_for_ip is covered by + # test_wait_for_ip_falls_back_to_arp below. out = (" Name MAC address Protocol Address\n" "-------------------------------------------------------------------\n" " vnet0 52:54:00:de:ad:be ipv4 192.168.150.73/24\n") assert ivm.parse_lease_ip(out) == "192.168.150.73" +def test_wait_for_ip_falls_back_to_arp(monkeypatch): + # wait_for_ip polls virsh domifaddr with --source lease first, then --source arp. + # Simulate lease returning empty (no DHCP lease yet) and arp returning a real address. + arp_out = (" Name MAC address Protocol Address\n" + "-------------------------------------------------------------------\n" + " vnet0 52:54:00:aa:bb:cc ipv4 192.168.150.142/24\n") + + def fake_sh(cmd, **kwargs): + if "arp" in cmd: + return types.SimpleNamespace(stdout=arp_out) + return types.SimpleNamespace(stdout="") + + monkeypatch.setattr(ivm, "sh", fake_sh) + monkeypatch.setattr(ivm.time, "sleep", lambda _: None) + assert ivm.wait_for_ip("dummy") == "192.168.150.142" + + def test_meta_data_has_instance_and_hostname(): md = ivm.render_meta_data("iid-askari-x", "boma-it-askari-x") assert "instance-id: iid-askari-x" in md