Removing one record of a unit gave back what that record found, even while another declared service holds the unit: when the private network's docker.service record goes and the docker module's stays, a record that found the runtime stopped would stop it, and every container with it, only for the docker module to start it again in the same apply. The record is forgotten instead, and the plan says so. A test pins the registry member moving from the network's record to the docker module's in one apply without leaving the list.
130 lines
5.2 KiB
Go
130 lines
5.2 KiB
Go
package apply
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-host/internal/declaration"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
)
|
|
|
|
// novox/hq issue 190, ADR 0222: the private network stops writing the mesh's registry into the
|
|
// container runtime's file, and the runtime's own module writes the same member. Both are moved in
|
|
// one apply, and the machine never loses the member: the network's record goes first (its member
|
|
// leaves the list), the runtime's module is applied after (the member is added back, now recorded
|
|
// as the runtime's), and the daemon is reloaded once, never stopped, disabled or restarted — even
|
|
// though the record going says the unit was found stopped and disabled.
|
|
|
|
const theRegistry = "anchor.internal:5100"
|
|
|
|
func runtimeDecl(t *testing.T, path string, network bool) *declaration.Declaration {
|
|
t.Helper()
|
|
var resources []string
|
|
if network {
|
|
resources = append(resources,
|
|
fmt.Sprintf(`{"id":"networking.registry-trust","type":"file","path":%q,"into":"json","content":%q}`,
|
|
path, `{"insecure-registries":["`+theRegistry+`"]}`),
|
|
`{"id":"networking.registry-trust-reload","type":"service","unit":"docker.service","state":"running",
|
|
"reload-on":["networking.registry-trust"]}`)
|
|
}
|
|
resources = append(resources,
|
|
fmt.Sprintf(`{"id":"docker.daemon","type":"file","path":%q,"into":"json","content":%q}`,
|
|
path, `{"live-restore":true,"insecure-registries":["`+theRegistry+`"]}`),
|
|
`{"id":"docker.runtime","type":"service","unit":"docker.service","state":"running","boot":"enabled",
|
|
"reload-on":["docker.daemon"]}`)
|
|
return parse(t, `{"declaration":1,"resources":[`+strings.Join(resources, ",")+`]}`)
|
|
}
|
|
|
|
func TestTheRegistryMovesToTheRuntimesModuleWithoutLeavingTheList(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "daemon.json")
|
|
if err := os.WriteFile(path, []byte(`{"insecure-registries":["192.0.2.7:5000"]}`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var commands []string
|
|
run := recordingServices(&commands)
|
|
|
|
// Both declare it: the network's record added the member, so the runtime's finds it there.
|
|
_, state, err := Apply(context.Background(), archHost(t), runtimeDecl(t, path, true), store.State{},
|
|
store.OriginDeclared, run, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Say the network's record found the runtime stopped and disabled: giving that back would stop
|
|
// every container on the machine.
|
|
for i := range state.Resources {
|
|
if state.Resources[i].ID == "networking.registry-trust-reload" {
|
|
state.Resources[i].Found = &store.FoundUnit{Unit: "docker.service", State: "stopped", Boot: "disabled"}
|
|
}
|
|
}
|
|
|
|
commands = nil
|
|
report, state, err := Apply(context.Background(), archHost(t), runtimeDecl(t, path, false), state,
|
|
store.OriginDeclared, run, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := fmt.Sprint(readObject(t, path)["insecure-registries"]); got != "[192.0.2.7:5000 "+theRegistry+"]" {
|
|
t.Fatalf("the list after the move: %s", got)
|
|
}
|
|
rec, _ := state.Find("docker.daemon")
|
|
if added := rec.Into.Added["insecure-registries"]; len(added) != 1 || canonical(added[0]) != `"`+theRegistry+`"` {
|
|
t.Errorf("the member is not recorded as the runtime's module's: %s", added)
|
|
}
|
|
reloads := 0
|
|
for _, c := range commands {
|
|
if strings.Contains(c, "docker.service") && (strings.Contains(c, " stop ") || strings.Contains(c, " restart ") ||
|
|
strings.Contains(c, " disable ")) {
|
|
t.Errorf("the runtime was given back as the network's record found it: %q", c)
|
|
}
|
|
if strings.Contains(c, "reload docker.service") {
|
|
reloads++
|
|
}
|
|
}
|
|
if reloads != 1 {
|
|
t.Errorf("the runtime was reloaded %d times; commands were %v", reloads, commands)
|
|
}
|
|
for _, o := range report.Outcomes {
|
|
if o.ID == "networking.registry-trust-reload" && o.Action != "forgotten" {
|
|
t.Errorf("the network's record of the unit was %q: %s", o.Action, o.Detail)
|
|
}
|
|
}
|
|
|
|
// Steady from here on, and undeclaring the runtime's module takes only what it added.
|
|
report, state, err = Apply(context.Background(), archHost(t), runtimeDecl(t, path, false), state,
|
|
store.OriginDeclared, run, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, o := range report.Outcomes {
|
|
if o.ID == "docker.daemon" && o.Action != "unchanged" {
|
|
t.Errorf("a second apply was %q", o.Action)
|
|
}
|
|
}
|
|
if _, _, err := Apply(context.Background(), archHost(t), somethingElse(t), state, store.OriginDeclared, run, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := fmt.Sprint(readObject(t, path)["insecure-registries"]); got != "[192.0.2.7:5000]" {
|
|
t.Errorf("undeclaring the runtime's module left %s", got)
|
|
}
|
|
}
|
|
|
|
// And the preview says the same before it happens.
|
|
func TestThePlanForgetsARecordOfAUnitStillHeld(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "daemon.json")
|
|
var commands []string
|
|
_, state, err := Apply(context.Background(), archHost(t), runtimeDecl(t, path, true), store.State{},
|
|
store.OriginDeclared, recordingServices(&commands), nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, step := range Plan(runtimeDecl(t, path, false), state, store.OriginDeclared) {
|
|
if step.ID == "networking.registry-trust-reload" && step.Verb != "forget" {
|
|
t.Errorf("the plan would %s the network's record of a unit docker.runtime holds: %s", step.Verb, step.Why)
|
|
}
|
|
}
|
|
}
|