Skip to content

Commit dc742ba

Browse files
committed
feat: carry the libvirt VNC port range without confext
libvirt has no /usr fallback for qemu.conf, and the base ships no libvirt, so a host that merges the qemu extension runs on the compiled defaults and loses the 32200-32299 policy the legacy image set. A confext would carry it, but a merged confext mounts /etc read-only, which is a poor fit for a hypervisor host. Ship the policy as a template in the qemu extension and materialize it with a tmpfiles C rule. The libvirt activation shim already runs systemd-tmpfiles --create after the merge and before libvirtd starts, so the file exists when the daemon reads it. Type C is copy-once: the first activation creates the file and later activations leave operator edits alone; a policy change needs the file removed or the rule moved to C+. Signed-off-by: Fabian Wiesel <fabian.wiesel@sap.com>
1 parent 00a1df9 commit dc742ba

5 files changed

Lines changed: 84 additions & 11 deletions

File tree

‎docs/sysext-virt.md‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ its own files.
6262
| `sci-22-storage` | multipath, iSCSI and NFS client tuning | `base` |
6363
| `sci-40-libvirt` | the hypervisor-agnostic libvirt daemon and clients, with vTPM | `base` |
6464
| `sci-50-virt-ch` | the Cloud Hypervisor driver and its firmware | `base` |
65-
| `sci-51-virt-qemu` | the qemu flavour: driver and userland | `base` |
65+
| `sci-51-virt-qemu` | the qemu flavour: driver and userland, plus the VNC port policy for `etc/libvirt/qemu.conf` | `base` |
6666

6767
**The prefixes encode the merge order.** systemd-sysext merges the
6868
deployed raws (`/var/lib/extensions/<name>.raw`) in lexical filename
@@ -419,6 +419,15 @@ and kmod already read. That keeps `/etc` writable and avoids confext.
419419
Confext is not used: it mounts `/etc` read-only while merged, the
420420
riskiest semantic, and oneshot units cover the need.
421421

422+
The one `/etc` file with no `/usr` fallback is libvirt's
423+
`qemu.conf`. The qemu extension ships a template under
424+
`usr/share/sci/etc/` and its tmpfiles fragment carries
425+
`C /etc/libvirt/qemu.conf`, which the libvirt activation shim applies
426+
after the merge and before `libvirtd` starts. Type `C` is copy-once:
427+
the first activation creates the file, later activations leave
428+
operator edits alone, and a policy change needs the file deleted or
429+
the rule moved to `C+`.
430+
422431
**Payload fragment filenames are not identities.** The sci-prefixed
423432
fragments keep their pre-rename stems on purpose:
424433
`usr/lib/sysusers.d/sci-compute.conf`,
@@ -897,14 +906,16 @@ ignition-capable USI base.
897906
- **Single owner per path.** Overlay shadowing is silent. One owner
898907
plus the artifact and guest contracts is the only way to keep
899908
identities and configs from diverging across hosts.
900-
- **`/etc` content needs its own carrier.** A sysext merges `/usr`
901-
only, so configuration the legacy image baked into `/etc` has no
902-
delivery path here. The qemu driver's VNC port range in
903-
`/etc/libvirt/qemu.conf` is the canonical case. Configuration
904-
extensions (confexts) are the candidate mechanism. The 2150 base
905-
ships `systemd-confext` (systemd 259.7), so the runtime support
906-
exists. Whether confexts arrive as a separate artifact set or as
907-
manifest entries is an open design decision.
909+
- **`/etc` content has narrow carriers.** A sysext merges `/usr` only.
910+
Most configuration has a `/usr/lib` fallback, and the one file that
911+
does not — libvirt's `qemu.conf` — is materialized at activation by a
912+
tmpfiles `C` rule from a template in the qemu extension. That carrier
913+
is copy-once and fits single files. Broader `/etc` content still needs
914+
a decision: confexts can carry it, but a merged confext mounts `/etc`
915+
read-only, the riskiest semantic for a hypervisor host. The 2150 base
916+
ships `systemd-confext` (259.7), so the runtime support exists.
917+
Whether confexts arrive as a separate artifact set or as manifest
918+
entries remains open.
908919
- **Sequential composition over one big image.** Separating identities,
909920
networking, storage and libvirt means a change to one does not churn
910921
the others. `sci-20-compute` keeps the chart-consumed UID contract

‎extensions/tests/guest/test_sysext.py‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ def test_delivery_and_host_contract():
6262
first = run("bash", str(staging / "sci-delivery.sh"), env=env)
6363
assert first.returncode == 0, first.stdout + first.stderr
6464
assert merged_extensions() == EXTENSIONS
65+
qemu_conf = Path("/etc/libvirt/qemu.conf")
66+
assert qemu_conf.is_file() and not qemu_conf.is_symlink(), qemu_conf
67+
qemu_text = qemu_conf.read_text()
68+
assert "remote_display_port_min = 32200" in qemu_text
69+
assert "remote_display_port_max = 32299" in qemu_text
6570
assert {p: Path(p).read_bytes() for p in base} == base
6671
# (uid, home, shell) as the sysusers fragments declare; libvirt-qemu's '-' is the default.
6772
identities = {
@@ -134,7 +139,31 @@ def test_delivery_and_host_contract():
134139
assert not db.is_symlink() and db.read_bytes() == b"existing OVS database\n"
135140
else:
136141
assert db.is_symlink() and os.readlink(db) == "/var/lib/openvswitch/conf.db"
137-
files = list(destination.glob("*.raw")) + [Path("/etc/passwd"), Path("/etc/group")]
142+
# The qemu C rule copies once: absent destinations are materialized, edits survive.
143+
qemu_fragment = Path("/usr/lib/tmpfiles.d/sci-virt-qemu.conf")
144+
qemu_payload = Path("/usr/share/sci/etc/libvirt/qemu.conf")
145+
assert qemu_fragment.is_file(), qemu_fragment
146+
assert qemu_payload.is_file(), qemu_payload
147+
for existing in (True, False):
148+
root = scratch / f"qemu-{existing}"
149+
source = root / "usr/share/sci/etc/libvirt/qemu.conf"
150+
source.parent.mkdir(parents=True)
151+
source.write_bytes(qemu_payload.read_bytes())
152+
target = root / "etc/libvirt/qemu.conf"
153+
if existing:
154+
target.parent.mkdir(parents=True)
155+
target.write_bytes(b"operator override\n")
156+
result = run("systemd-tmpfiles", f"--root={root}", "--create", str(qemu_fragment))
157+
assert result.returncode == 0, result.stderr
158+
if existing:
159+
assert not target.is_symlink() and target.read_bytes() == b"operator override\n"
160+
else:
161+
assert target.is_file() and not target.is_symlink()
162+
copied = target.read_text()
163+
assert "remote_display_port_min = 32200" in copied
164+
assert "remote_display_port_max = 32299" in copied
165+
files = list(destination.glob("*.raw")) + [Path("/etc/passwd"), Path("/etc/group"),
166+
Path("/etc/libvirt/qemu.conf")]
138167
files += [Path(f"/etc/systemd/system/{name}-activation.service")
139168
for name in ("sci-20-compute", "sci-21-network", "sci-22-storage", "sci-40-libvirt")]
140169
before = {p: (p.read_bytes(), p.stat().st_mtime_ns) for p in files}

‎extensions/tests/test_artifact_contracts.py‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"usr/share/cloud-hypervisor/CLOUDHV_EFI.fd"),
3232
"sci-51-virt-qemu": ("usr/bin/qemu-system-x86_64", "usr/bin/qemu-img", "usr/bin/qemu-nbd",
3333
"usr/share/OVMF/OVMF_CODE_4M.fd",
34-
"usr/lib/x86_64-linux-gnu/libvirt/connection-driver/libvirt_driver_qemu.so"),
34+
"usr/lib/x86_64-linux-gnu/libvirt/connection-driver/libvirt_driver_qemu.so",
35+
"usr/share/sci/etc/libvirt/qemu.conf"),
3536
}
3637
# Base-owned content in the merged host view; a delta must not re-ship it.
3738
BASE_OWNED = {
@@ -186,6 +187,29 @@ def test_static_input_contract():
186187
run("git", "check-ignore", "-q", "--", str(key_path))
187188

188189

190+
def test_qemu_vnc_range_carrier():
191+
extra = ROOT / "mkosi/mkosi.images/sci-51-virt-qemu/mkosi.extra"
192+
rules = [line.strip() for line in
193+
(extra / "usr/lib/tmpfiles.d/sci-virt-qemu.conf").read_text().splitlines()]
194+
payload = extra / "usr/share/sci/etc/libvirt/qemu.conf"
195+
assert payload.is_file(), f"the qemu VNC range payload must exist at {payload}"
196+
lines = [line.strip() for line in payload.read_text().splitlines()]
197+
assert any(re.fullmatch(r"d\s+/etc/libvirt\s+0755\s+root\s+root\s+-", line) for line in rules), (
198+
"the qemu fragment must create /etc/libvirt before the C rule copies into it")
199+
assert any(re.fullmatch(
200+
r"C\s+/etc/libvirt/qemu\.conf\s+0644\s+root\s+root\s+-\s+/usr/share/sci/etc/libvirt/qemu\.conf",
201+
line) for line in rules), (
202+
"the qemu fragment must materialize /etc/libvirt/qemu.conf from the /usr payload with type C")
203+
assert sum("remote_display_port" in line for line in lines) == 2, (
204+
"the payload must declare exactly the two VNC port settings")
205+
assert sum(bool(re.fullmatch(r"remote_display_port_min\s*=\s*32200", line)) for line in lines) == 1, (
206+
"the payload must set remote_display_port_min = 32200 for the NodePort range")
207+
assert sum(bool(re.fullmatch(r"remote_display_port_max\s*=\s*32299", line)) for line in lines) == 1, (
208+
"the payload must set remote_display_port_max = 32299 for the NodePort range")
209+
assert not [line for line in lines if line.startswith("#") and "remote_display_port" in line], (
210+
"the payload must be a partial conffile with no commented-out port settings")
211+
212+
189213
@pytest.mark.build
190214
def test_tools_manifest_pin():
191215
manifest = STATE / "tools/mkosi.tools.manifest"

‎mkosi/mkosi.images/sci-51-virt-qemu/mkosi.extra/usr/lib/tmpfiles.d/sci-virt-qemu.conf‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@
66
# paths must survive every tmpfiles run.
77
d /var/lib/libvirt/qemu 0755 root root -
88
d /var/log/libvirt/qemu 0755 root root -
9+
# libvirt's qemu.conf has no /usr fallback: materialize the VNC port
10+
# policy at activation. Type C is copy-once, so operator edits survive.
11+
d /etc/libvirt 0755 root root -
12+
C /etc/libvirt/qemu.conf 0644 root root - /usr/share/sci/etc/libvirt/qemu.conf
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# SCI: keep libvirt's automatic VNC ports inside the Kubernetes NodePort
2+
# range. Materialized by a tmpfiles C rule at activation; the copy is
3+
# created only when /etc/libvirt/qemu.conf does not exist yet.
4+
remote_display_port_min = 32200
5+
remote_display_port_max = 32299

0 commit comments

Comments
 (0)