Extract the repeated dnsmasq pidfile path literals in internal/networ... - #148
Merged
colonelpanik merged 1 commit intoAug 9, 2026
Conversation
colonelpanik
marked this pull request as ready for review
August 9, 2026 19:03
colonelpanik
force-pushed
the
overseer/extract-the-repeated-dnsmasq-pidfile-path-literals-in-intern
branch
from
August 9, 2026 19:03
3408568 to
383e5b6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal
Extract the repeated dnsmasq pidfile path literals in internal/network/provision.go (fmt.Sprintf("/var/run/litevirt-dnsmasq-%s.pid", bridge) at lines 101, 245, 305 and fmt.Sprintf("/var/run/litevirt-dnsmasq-vni%d.pid", vni) at lines 191, 335) into one or two small named helpers in internal/network, and use them at every call site.
Plan
Plan: extract the dnsmasq pidfile path literals in
internal/networkGoal
Replace the hand-rolled
fmt.Sprintfdnsmasq pidfile paths ininternal/network/provision.gowith two small unexported helpers ininternal/network, and use them at every call site.This is a pure extraction. The produced strings must stay byte-for-byte identical
to what
internal/network/dnsmasq.go's pidfile-based stop/kill logic(
StopDHCP,procIsOurDnsmasq,readPidFile, the drift-restart path) andinternal/network/dnsmasq_test.goalready expect. No path-format change, nobehaviour change, no new exported API.
Current state
The literals live in exactly one non-test file. Verified with
grep -rn "litevirt-dnsmasq" --include=*.go .:Provision,"bridge"case, DHCP startfmt.Sprintf("/var/run/litevirt-dnsmasq-%s.pid", bridge)Provision,"vxlan"case, DHCP start on the elected gateway hostfmt.Sprintf("/var/run/litevirt-dnsmasq-vni%d.pid", vni)Provision,"isolated"case, DHCP startfmt.Sprintf("/var/run/litevirt-dnsmasq-%s.pid", bridge)Deprovision,"bridge"case,StopDHCPfmt.Sprintf("/var/run/litevirt-dnsmasq-%s.pid", bridge)Deprovision,"vxlan"case,StopDHCPfmt.Sprintf("/var/run/litevirt-dnsmasq-vni%d.pid", vni)Deprovision,"isolated"case,StopDHCPfmt.Sprintf("/var/run/litevirt-dnsmasq-%s.pid", bridge)There are six sites, not the five named in the task. Line 346 (the
"isolated"branch ofDeprovision) is the same%s-with-bridge form and isincluded — "every call site" is the requirement, and leaving one behind would
defeat the point of the extraction.
Nothing outside
internal/networkconstructs one of these paths.StartDHCP/StopDHCPare exported but their only callers are inside thispackage (
provision.goanddnsmasq.goitself), so both helpers stayunexported, as the constraint requires.
Design
Add to
internal/network/dnsmasq.go, immediately after the existingdnsmasqLeaseDirconst (~line 65). That file already owns every consumer of apidfile path and the sibling "where does dnsmasq's on-disk state live" fact, so
the two path-shape decisions end up adjacent and reviewable together.
Then, at each of the six sites, replace only the right-hand side; keep the local
pidFile :=binding so the diff is six one-line changes:pidFile := dnsmasqPidFile(bridge)pidFile := dnsmasqPidFileVNI(vni)Why the VNI helper delegates rather than holding its own literal
Delegating leaves exactly one path literal in the package, which is the whole
point of the change. The alternative — a second independent
fmt.Sprintf("/var/run/litevirt-dnsmasq-vni%d.pid", vni)insidednsmasqPidFileVNI— reads marginally more directly but leaves two copies of/var/run/litevirt-dnsmasq-that can drift apart, which is the defect beingremoved.
"vni" + strconv.Itoa(vni)composed through the base helper isbyte-identical to the old
vni%dform for every int (%dandItoaagree,including negatives, which cannot occur here —
Provisionrejectsvni == 0andthe VNI comes from a validated compose field).
The base helper's parameter is named
key, notbridge, because two of sixcallers pass a VNI token. Naming it
bridgewould make those two call sites readas a bug.
Byte-identity argument
dnsmasqPidFile(bridge)≡fmt.Sprintf("/var/run/litevirt-dnsmasq-%s.pid", bridge)—
%son astringis the identity, so concatenation is equivalent.dnsmasqPidFileVNI(vni)≡fmt.Sprintf("/var/run/litevirt-dnsmasq-vni%d.pid", vni)— expands to
"/var/run/litevirt-dnsmasq-" + "vni" + itoa(vni) + ".pid"./var/runis preserved verbatim. It is not normalised to/run(they arethe same directory via symlink on modern Linux, but
procIsOurDnsmasqand thepkill backstop match the argv string, so a live dnsmasq started by the
previous binary with
--pid-file=/var/run/...would stop matching).Files to change
internal/network/dnsmasq.go— add the two helpers (~14 lines incl. comments).fmtis already imported.internal/network/provision.go— six one-line replacements.fmtstaysimported (used by
fmt.Errorfthroughout), so no import churn and nogoimportsmovement.internal/network/provision_test.go— new call-site tests (below).internal/network/dnsmasq_test.go— new helper unit test (below).No other file changes. No docs, schema, ledger, proto, or CLI surface is touched,
so
make ci-guardsis unaffected (it still must pass, and will — there is no newconfig key, flag, doc reference, SQL builder, or
litevirt_*identifier).Tests
Unit tier, in
internal/network. This is package-local logic with no multi-nodefailure mode, so per
CLAUDE.md's tier tabletests/fleet/is not the rightreach here — a fleet node never spawns dnsmasq (it runs
libvirtfake), so afleet test could not observe these strings at all.
1.
TestDnsmasqPidFile(new, indnsmasq_test.go)Pins the exact output of both helpers and the relationship between them:
Plus the load-bearing negative assertion:
That last line is the guard against the one way this refactor can go wrong later:
a "simplification" that notices a
bridgevariable in scope at the VXLAN sitesand collapses both helpers into one. It fails loudly with a comment explaining
the upgrade hazard.
Also assert round-tripping through the consumer, so the test proves the helper
output is usable by the stop/kill logic rather than merely equal to a string:
2.
TestProvision_DnsmasqPidFilePaths(new, inprovision_test.go)A helper-unit test alone would not catch "helper added, but one call site still
hand-rolls its own path". Every one of the six sites is independently observable,
so each gets an assertion.
The three
Provisionsites (101, 191, 245) — stubstartDHCPFuncto capturethe
pidFileargument (the existing pattern atprovision_test.go:79and:181) and stubexecCommand:NetworkDefpidFile{Type:"bridge", Interface:"lv-pf-br0", Subnet:"10.88.0.0/24"}/var/run/litevirt-dnsmasq-lv-pf-br0.pid{Type:"vxlan", VNI:4242, Underlay:"eth0", Subnet:"10.89.0.0/24"}/var/run/litevirt-dnsmasq-vni4242.pid{Type:"isolated", Subnet:"10.90.0.0/24"}, network"pfnet"/var/run/litevirt-dnsmasq-br-iso-pfnet.pidReachability of each DHCP start, checked against the code:
lv-pf-br0does not exist on the host, soBridgeExists(a realnet.InterfaceByName, notexecCommand) is false →bridgePreExistedfalse →the
Subnet != "" && (!bridgePreExisted || def.DHCP)gate opens. Keep the name≤15 chars or
EnsureBridgerejects it.Underlayexplicitly sodefaultRouteInterface()(which wouldreturn
""under the exec stub and abort with "could not auto-detect") is notconsulted.
Provisionupserts this host's own VTEP, soisGatewayHostsees asingle-host list and elects it → DHCP starts. This path sleeps 500 ms for CRDT
convergence, same as the existing
TestProvision_WithSubnet.HostIsolationbranch callscorrosion.DeleteHostFWIntent, so this subtest needs a realcorrosion.NewTestClient()+InitSchema(as all three do).The three
Deprovisionsites (305, 335, 346) — these callStopDHCPdirectly, not through a func var, so they are observed one level down:
StopDHCP's pkill backstop runsexecCommand("pkill", "-f", "pid-file="+pidFile),and
execCommandis stubbed. Assert the captured call set contains exactly:NetworkDef{Type:"bridge", Interface:"lv-pf-br0", Subnet:"10.88.0.0/24"}pid-file=/var/run/litevirt-dnsmasq-lv-pf-br0.pid{Type:"vxlan", VNI:4242, Subnet:"10.89.0.0/24"}pid-file=/var/run/litevirt-dnsmasq-vni4242.pid{Type:"isolated", Subnet:"10.90.0.0/24"}, network"pfnet"pid-file=/var/run/litevirt-dnsmasq-br-iso-pfnet.pidDeprovisioncan be called with anildb here — its only db use is guarded byif db != nil. Every other side effect (RemoveHostIsolation,RemoveSNAT,RemoveProxyARP,RemoveNAT,RemoveIRB,DeprovisionVXLAN,ip link del)goes through
execCommandand is therefore fully stubbed.Test-hygiene notes for whoever writes these
execCommandandstartDHCPFuncare package-level vars. Subtests must notcall
t.Parallel(), and each mustdeferthe restore(
execCommand = defaultExec,startDHCPFunc = StartDHCP) exactly like theexisting tests.
StopDHCPalso does a realos.ReadFile/os.Removeon the path (only thepkillgoes through the stub). On a non-root dev box both fail with ENOENT andare ignored. Use deliberately unreal names (
lv-pf-br0, VNI4242, networkpfnet) so the test cannot delete a real pidfile if someone runsgo test ./internal/network/as root on a live litevirt host.IsolatedBridgeName("pfnet")→"br-iso-pfnet"(12 chars, under the hashfallback threshold), so the expected string is stable and readable. Do not pick
a long network name here or the bridge becomes a sha1 prefix.
Mutation verification
Per
CLAUDE.md, each assertion is proven by breaking the property and confirmingred, then restoring. Three mutations, each targeting a different failure the tests
are supposed to catch:
"/run/litevirt-dnsmasq-".Expected red:
TestDnsmasqPidFileand all six call-site assertions. Provesthe tests pin the literal path rather than just "whatever the helper returns".
dnsmasqPidFileVNItoreturn dnsmasqPidFile(vxlanBridgeName(vni)). Expected red: the!=assertion inTestDnsmasqPidFileplus the two vxlan subtests. Proves thenegative assertion is not vacuous — this is the mutation that matters most,
because it is the plausible future mistake.
fmt.Sprintf("/var/run/litevirt-dnsmasq-vni-%d.pid", vni)(note the extrahyphen). Expected red: the vxlan
Provisionsubtest only. Proves thecall-site tests observe the actual call sites, not just the helper. Repeat for
one
Deprovisionsite (335) to prove the pkill-based observation works.Record the observed red output for at least mutations 2 and 3 before restoring —
those are the two that make the test suite non-vacuous.
Verification
Plus the mechanical check that the extraction is complete — after the change this
must print exactly one line (the helper in
dnsmasq.go):Notes for the reviewer
vni500vsbr-vni500. BothProvision's andDeprovision's vxlan branches have abridgevariable in scope (bridge, err := EnsureVXLAN(...)/bridge := vxlanBridgeName(vni)), which makesdnsmasqPidFile(bridge)looklike the obvious tidy-up at those two sites. It is not: it changes the path.
The concrete blast radius is an upgrade — a node running the current binary
has live dnsmasq processes whose command line carries
--pid-file=/var/run/litevirt-dnsmasq-vni500.pid. A new binary looking at.../litevirt-dnsmasq-br-vni500.pidwould find no pidfile, skip thealready-running short-circuit, fail
procIsOurDnsmasq, miss with the pkillbackstop, and then spawn a second dnsmasq that dies on bind against the
survivor still holding the bridge gateway IP:53 — DHCP outage on every VXLAN
network with a subnet, and
StartDHCP's two-attempt retry does not help. Thisis why the two helpers stay distinct and why the
!=assertion exists.dnsmasq_test.go's existing literals stay literal, on purpose.TestDnsmasqArgs_ExcludesLoopback(:84, :90),TestDnsmasqArgs_V6EnablesRA(:151), and
TestCmdlineHasPidFile(:15, :21) pass pidfile paths in asinputs to
dnsmasqArgs/cmdlineHasPidFile; they are testing argumentconstruction and argv matching, not path derivation. Rewriting them to call
dnsmasqPidFilewould make them tautological — a path-format regression wouldmove both sides of the comparison and the tests would still pass. Leaving them
as independent literals is what keeps them a real guard on the constraint.
(
TestCmdlineHasPidFileuses/run/..., not/var/run/...; that is fine andintentional — it only needs some path shape to test exact-argv matching.)
/var/runis load-bearing as a string. See the byte-identity section: thestop/kill paths match argv text, so
/runis not a safe "cleanup".intent decoupling (
b7e46c7, pinned byTestProvision_Bridge_PreExistingNAT_NoDHCP), the gateway election, and thedrift-restart logic are all untouched. If a reviewer sees any of those move,
that is out of scope.
directory (
internal/lbuses/run/litevirt/lbwith an injectablerunDir—a tempting symmetry, but it changes the on-disk contract and breaks the
byte-identity constraint), unifying with
dnsmasqLeaseDir, and threading apidfile through
StartDHCP's signature so callers cannot get it wrong.refactor(network): extract the dnsmasq pidfile path into one helper.PLAN.mdis already gitignored (.gitignore:71), so it will not appear in thediff.
Deviations from the plan as written
Two, both cosmetic; the design, the six call sites, and every test are as
specified.
The completeness grep prints two lines, not one. The plan's check
(
grep -rn "litevirt-dnsmasq-" --include=*.go internal/ | grep -v _test.go)also matches
dnsmasq.go:88, a comment line insidednsmasqPidFileVNI's doc block that names.../litevirt-dnsmasq-br-vni500.pidas the concrete counter-example for the collapse hazard. The plan's intent —
one place where the path is constructed — holds exactly; excluding comment
lines prints the single
dnsmasq.go:80return. The counter-example path waskept because a reader hitting that comment benefits from seeing the wrong path
spelled out, which is the whole point of the warning.
Pre-existing
gofmtnon-compliance was left alone.gofmt -l internal/network/flagsdnsmasq.go,provision_test.go,bridge.go,ipam.goandnetwork_extra_test.go. All of it predates this change and isidentical at
HEAD(verified per-file againstgit show HEAD:<file> | gofmt -d): indnsmasq.goa missing//separator in the comment block abovednsmasqLeaseDir, inprovision_test.gofourdef,"10.0.0.1"calls missing aspace. The added code introduces zero new
gofmtfindings. Fixing the restwould have put unrelated churn in a pure-extraction diff.
Mutation verification (observed, then restored)
All three mutations behaved exactly as predicted.
/run/for/var/run/) → red: all 3TestDnsmasqPidFileequality assertions and all six call-site assertions.
dnsmasqPidFileVNI→dnsmasqPidFile(vxlanBridgeName(vni))) →red:
dnsmasq_test.go:64(VXLAN pidfile must key on the VNI token, not the bridge name; both produced "/var/run/litevirt-dnsmasq-br-vni500.pid"), thevni500equality assertion, plusprovision_vxlananddeprovision_vxlanonly — the bridge and isolated subtests correctly stayed green. The negative
assertion is not vacuous.
vni-%d, extra hyphen), applied one site at a time:site 191 → only
provision_vxlanred (got .../litevirt-dnsmasq-vni-4242.pid);site 335 → only
deprovision_vxlanred, via thepkillpattern(
pid-file=/var/run/litevirt-dnsmasq-vni-4242.pid), confirming theone-level-down observation of the
Deprovisionsites actually works.go build ./...,go vet ./...,go test ./...andmake ci-guards(incl.stmtshapecheck, ledger-drift, writecheck, docs truth) all pass.Final Codex review
No blocking findings remained.
Opened by overseer. The plan and the code each converged to zero blocking findings from Codex before this pull request was created.