Adoption mode: a node in use is adopted before it is converged (hq ADR 0100–0103) #20
@@ -202,6 +202,24 @@ func ApplyKeeping(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **A hold whose resource is no longer declared is let go, and nothing on disk is touched.**
|
||||||
|
// What was found stays as it was; only the host's note that it holds it for a module goes, so
|
||||||
|
// the node stops reporting a hold for a module no longer assigned. Should the module come back,
|
||||||
|
// what is there is present with no record and is found, and held, again — its first kept
|
||||||
|
// original is never overwritten (novox/hq ADR 0100). Only a declaration from the mesh says
|
||||||
|
// what is assigned: a carried bundle's silence is not an unassignment.
|
||||||
|
if origin == store.OriginDeclared {
|
||||||
|
for _, h := range append([]store.Held{}, known.Held...) {
|
||||||
|
if declared[h.ID] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
known.Release(h.ID)
|
||||||
|
report.Outcomes = append(report.Outcomes, Outcome{ID: h.ID, Type: h.Kind, Target: h.Target,
|
||||||
|
Action: "forgotten", Detail: "no longer declared; left as found"})
|
||||||
|
log(fmt.Sprintf(" forgotten %s (%s): no longer declared; left as found", h.ID, h.Target))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// What moved in this apply, so a service that must reflect a file can be told the file
|
// What moved in this apply, so a service that must reflect a file can be told the file
|
||||||
// moved. Only within one apply: a change from an earlier one has already been reflected, and
|
// moved. Only within one apply: a change from an earlier one has already been reflected, and
|
||||||
// restarting for it every time would make a steady machine restart its services for ever.
|
// restarting for it every time would make a steady machine restart its services for ever.
|
||||||
|
|||||||
+135
-2
@@ -332,8 +332,141 @@ func TestAHeldFileIsNeverRemovedWhenItsModuleIsUnassigned(t *testing.T) {
|
|||||||
if m.removed("hello-web") {
|
if m.removed("hello-web") {
|
||||||
t.Fatal("a held container was removed when its module left")
|
t.Fatal("a held container was removed when its module left")
|
||||||
}
|
}
|
||||||
if _, still := state.HeldAt("hello-web.page"); !still {
|
if _, still := state.HeldAt("hello-web.page"); still {
|
||||||
t.Error("the hold was forgotten, so a return of the module would read the file as the mesh's")
|
t.Error("a hold outlived its resource leaving the declaration, so the node reports it for ever")
|
||||||
|
}
|
||||||
|
if _, recorded := state.Find("hello-web.page"); recorded {
|
||||||
|
t.Error("a file let go was recorded as the mesh's")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAHoldNoLongerDeclaredIsLetGoAndFoundAgainIfItsModuleReturns(t *testing.T) {
|
||||||
|
dir, page, m := predecessor(t)
|
||||||
|
_, state := applyAdopted(t, adopted(t, untakenWeb, webResources(page)), store.State{}, m, dir)
|
||||||
|
first, _ := state.HeldAt("hello-web.page")
|
||||||
|
|
||||||
|
report, state := applyAdopted(t, adopted(t, `{"taken":[]}`, withConf(dir)), state, m, dir)
|
||||||
|
if o := outcomeOf(report, "hello-web.page"); o.Action != "forgotten" || o.Detail != "no longer declared; left as found" {
|
||||||
|
t.Errorf("letting a hold go was not reported: %+v", o)
|
||||||
|
}
|
||||||
|
if len(state.Held) != 0 {
|
||||||
|
t.Errorf("holds outlived their resources: %+v", state.Held)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The module comes back: what is there is found again, and the original first kept stays.
|
||||||
|
if err := os.WriteFile(page, []byte("the predecessor wrote again\n"), 0o640); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
report, state = applyAdopted(t, adopted(t, untakenWeb, webResources(page)), state, m, dir)
|
||||||
|
if o := outcomeOf(report, "hello-web.page"); o.Action != "held" {
|
||||||
|
t.Fatalf("a returning module's found file was not held again: %+v", o)
|
||||||
|
}
|
||||||
|
again, _ := state.HeldAt("hello-web.page")
|
||||||
|
if kept, _ := os.ReadFile(again.Kept); again.Kept != first.Kept || string(kept) != "the predecessor's page\n" {
|
||||||
|
t.Errorf("the first kept original was lost: %s %q", again.Kept, kept)
|
||||||
|
}
|
||||||
|
if got, _ := os.ReadFile(page); string(got) != "the predecessor wrote again\n" {
|
||||||
|
t.Errorf("the found file was touched: %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestACarriedApplyLetsNoHoldGo(t *testing.T) {
|
||||||
|
dir, page, m := predecessor(t)
|
||||||
|
_, state := applyAdopted(t, adopted(t, untakenWeb, webResources(page)), store.State{}, m, dir)
|
||||||
|
carried := parse(t, `{"declaration":1,"resources":[`+withConf(dir)+`]}`)
|
||||||
|
_, state, err := ApplyKeeping(context.Background(), archHost(t), carried, state, store.OriginCarried,
|
||||||
|
m.run, nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(state.Held) != 2 {
|
||||||
|
t.Errorf("a carried apply let holds go: %+v", state.Held)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAHeldFileRewrittenIsReportedAndNotReverted(t *testing.T) {
|
||||||
|
dir, page, m := predecessor(t)
|
||||||
|
d := adopted(t, untakenWeb, webResources(page))
|
||||||
|
_, state := applyAdopted(t, d, store.State{}, m, dir)
|
||||||
|
|
||||||
|
if err := os.WriteFile(page, []byte("the predecessor wrote again\n"), 0o640); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
report, state := applyAdopted(t, d, state, m, dir)
|
||||||
|
if got, _ := os.ReadFile(page); string(got) != "the predecessor wrote again\n" {
|
||||||
|
t.Fatalf("a held file was reverted: %q", got)
|
||||||
|
}
|
||||||
|
if h, _ := state.HeldAt("hello-web.page"); h.Changed != "rewritten" || h.ChangedAt.IsZero() {
|
||||||
|
t.Errorf("a rewrite was not recorded: %+v", h)
|
||||||
|
}
|
||||||
|
if o := outcomeOf(report, "hello-web.page"); !strings.Contains(o.Detail, "rewritten") {
|
||||||
|
t.Errorf("a rewrite was not reported: %+v", o)
|
||||||
|
}
|
||||||
|
h, _ := state.HeldAt("hello-web.page")
|
||||||
|
if kept, _ := os.ReadFile(h.Kept); string(kept) != "the predecessor's page\n" {
|
||||||
|
t.Errorf("the kept original was overwritten by a later write: %q", kept)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAHeldContainerStoppedOrReplacedIsReportedAndNotRestarted(t *testing.T) {
|
||||||
|
for _, c := range []struct {
|
||||||
|
change func(*machine)
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{func(m *machine) { m.containers["hello-web"].running = false }, "stopped"},
|
||||||
|
{func(m *machine) { m.containers["hello-web"].id = "another" }, "replaced"},
|
||||||
|
{func(m *machine) { delete(m.containers, "hello-web") }, "gone"},
|
||||||
|
} {
|
||||||
|
dir, page, m := predecessor(t)
|
||||||
|
d := adopted(t, untakenWeb, webResources(page))
|
||||||
|
_, state := applyAdopted(t, d, store.State{}, m, dir)
|
||||||
|
c.change(m)
|
||||||
|
m.asked = nil
|
||||||
|
_, state = applyAdopted(t, d, state, m, dir)
|
||||||
|
if h, _ := state.HeldAt("hello-web.server"); h.Changed != c.want {
|
||||||
|
t.Errorf("%s: recorded as %q", c.want, h.Changed)
|
||||||
|
}
|
||||||
|
for _, a := range m.asked {
|
||||||
|
if strings.HasPrefix(a, "docker run") || strings.HasPrefix(a, "docker rm") ||
|
||||||
|
strings.HasPrefix(a, "docker start") {
|
||||||
|
t.Errorf("%s: the held container was acted on: %s", c.want, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAHeldFileThatVanishesIsNotCreated(t *testing.T) {
|
||||||
|
dir, page, m := predecessor(t)
|
||||||
|
d := adopted(t, untakenWeb, webResources(page))
|
||||||
|
_, state := applyAdopted(t, d, store.State{}, m, dir)
|
||||||
|
if err := os.Remove(page); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, state = applyAdopted(t, d, state, m, dir)
|
||||||
|
if _, err := os.Stat(page); !errors.Is(err, os.ErrNotExist) {
|
||||||
|
t.Fatal("a held file that vanished was created before its module was taken")
|
||||||
|
}
|
||||||
|
if h, _ := state.HeldAt("hello-web.page"); h.Changed != "gone" {
|
||||||
|
t.Errorf("a vanished held file was not reported gone: %+v", h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAConvergedNodeStillReplacesWhatItFinds(t *testing.T) {
|
||||||
|
// No adoption, no holds: byte for byte what a converged node did before ADR 0100.
|
||||||
|
dir, page, m := predecessor(t)
|
||||||
|
d := parse(t, `{"declaration":1,"resources":[`+webResources(page)+`]}`)
|
||||||
|
report, state := applyAdopted(t, d, store.State{}, m, dir)
|
||||||
|
if got, _ := os.ReadFile(page); string(got) != "the mesh's page\n" {
|
||||||
|
t.Errorf("a converged node kept a found file: %q", got)
|
||||||
|
}
|
||||||
|
if !m.removed("hello-web") {
|
||||||
|
t.Error("a converged node kept a found container")
|
||||||
|
}
|
||||||
|
if len(state.Held) != 0 || outcomeOf(report, "hello-web.page").Action == "held" {
|
||||||
|
t.Errorf("a converged node held something: %+v", state.Held)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(dir, "kept")); !errors.Is(err, os.ErrNotExist) {
|
||||||
|
t.Error("a converged node kept originals")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user