Adoption mode: a node in use is adopted before it is converged (hq ADR 0100–0103) #20

Merged
jschoubben merged 52 commits from feat/adoption-mode into main 2026-09-22 19:01:47 +00:00
2 changed files with 33 additions and 4 deletions
Showing only changes of commit 60bb3d895c - Show all commits
+10 -3
View File
@@ -95,9 +95,16 @@ func lookBefore(ctx context.Context, sys system.System, d *declaration.Declarati
if known.Recorded(string(declaration.TypeService), res.Unit) { if known.Recorded(string(declaration.TypeService), res.Unit) {
continue continue
} }
// A unit the service manager cannot find is not there; one it can read is, whatever // Found is what the machine runs: a unit that is running or starts at boot. A unit
// state it is in. // file a package merely ships — a template instance nothing ever started, say the
if _, err := sys.ServiceState(ctx, run, res.Unit); err == nil { // private network's own wg-quick@mesh0 — is not a predecessor's service, and holding
// it kept the private network from ever coming up (found by the adoption bed).
state, err := sys.ServiceState(ctx, run, res.Unit)
if err != nil {
continue
}
boot, _ := sys.ServiceBoot(ctx, run, res.Unit)
if state == "running" || boot == "enabled" {
seen["unit:"+res.Unit] = true seen["unit:"+res.Unit] = true
} }
case *declaration.Container: case *declaration.Container:
+23 -1
View File
@@ -570,7 +570,8 @@ func TestAFoundServiceOfAnUntakenModuleIsNeitherStartedNorEnabledNorRestarted(t
dir := t.TempDir() dir := t.TempDir()
conf := filepath.Join(dir, "hello.conf") conf := filepath.Join(dir, "hello.conf")
m := &machine{containers: map[string]*fakeContainer{}, m := &machine{containers: map[string]*fakeContainer{},
units: map[string]*fakeUnit{"hello.service": {active: "inactive", enabled: "disabled"}}} // The predecessor's unit: stopped just now, but it starts at boot, so it is the machine's.
units: map[string]*fakeUnit{"hello.service": {active: "inactive", enabled: "enabled"}}}
report, state := applyAdopted(t, adopted(t, untaken("hello-web.conf", "hello-web.unit"), report, state := applyAdopted(t, adopted(t, untaken("hello-web.conf", "hello-web.unit"),
`{"id":"hello-web.conf","type":"file","path":"`+conf+`","content":"x\n"}, `{"id":"hello-web.conf","type":"file","path":"`+conf+`","content":"x\n"},
{"id":"hello-web.unit","type":"service","unit":"hello.service","state":"running","boot":"enabled", {"id":"hello-web.unit","type":"service","unit":"hello.service","state":"running","boot":"enabled",
@@ -610,6 +611,27 @@ func TestAHeldServiceIsStillReloadedButNeverRestarted(t *testing.T) {
} }
} }
func TestAUnitAPackageOnlyShipsIsNotFound(t *testing.T) {
// The adoption bed found this: the private network's wg-quick@mesh0 is an instance of a unit
// the tunnel package ships. Nothing had ever run it, yet it was held as a predecessor's, and
// the private network never came up. Found is what the machine runs.
dir := t.TempDir()
m := &machine{containers: map[string]*fakeContainer{},
units: map[string]*fakeUnit{"wg-quick@mesh0.service": {active: "inactive", enabled: "disabled"}}}
report, state := applyAdopted(t, adopted(t, untaken("mesh-wireguard.overlay-up"),
`{"id":"mesh-wireguard.overlay-up","type":"service","unit":"wg-quick@mesh0.service","state":"running","boot":"enabled"}`),
store.State{}, m, dir)
if o := outcomeOf(report, "mesh-wireguard.overlay-up"); o.Action == "held" {
t.Fatalf("a unit nothing runs was held as a predecessor's: %+v", o)
}
if !m.did("systemctl start wg-quick@mesh0.service") || !m.did("systemctl enable wg-quick@mesh0.service") {
t.Errorf("the unit was not started and enabled: %v", m.asked)
}
if len(state.Held) != 0 {
t.Errorf("held: %+v", state.Held)
}
}
func TestAServiceWhoseUnitIsNotThereIsAppliedAsUsual(t *testing.T) { func TestAServiceWhoseUnitIsNotThereIsAppliedAsUsual(t *testing.T) {
// No unit before the apply: nothing of a predecessor's to hold. // No unit before the apply: nothing of a predecessor's to hold.
dir := t.TempDir() dir := t.TempDir()