diff --git a/internal/apply/hold.go b/internal/apply/hold.go index f763dac..a71bf4d 100644 --- a/internal/apply/hold.go +++ b/internal/apply/hold.go @@ -95,9 +95,16 @@ func lookBefore(ctx context.Context, sys system.System, d *declaration.Declarati if known.Recorded(string(declaration.TypeService), res.Unit) { continue } - // A unit the service manager cannot find is not there; one it can read is, whatever - // state it is in. - if _, err := sys.ServiceState(ctx, run, res.Unit); err == nil { + // Found is what the machine runs: a unit that is running or starts at boot. A unit + // file a package merely ships — a template instance nothing ever started, say the + // 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 } case *declaration.Container: diff --git a/internal/apply/hold_test.go b/internal/apply/hold_test.go index 5c7b03a..b2efcb6 100644 --- a/internal/apply/hold_test.go +++ b/internal/apply/hold_test.go @@ -570,7 +570,8 @@ func TestAFoundServiceOfAnUntakenModuleIsNeitherStartedNorEnabledNorRestarted(t dir := t.TempDir() conf := filepath.Join(dir, "hello.conf") 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"), `{"id":"hello-web.conf","type":"file","path":"`+conf+`","content":"x\n"}, {"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) { // No unit before the apply: nothing of a predecessor's to hold. dir := t.TempDir()