An apply leaves the resources its scope keeps as they run, so the bus server is not replaced by an unwarranted want (ADR 0594 §2)
This commit is contained in:
+64
-4
@@ -143,8 +143,9 @@ func (r Report) Changed() bool {
|
||||
// (novox/hq issue 224).
|
||||
// waits busy is a container left running because it said busy (novox/hq ADR 0335), which nothing
|
||||
// moved either.
|
||||
// Kept as it runs is a bus server a want without the warrant's mark left alone (novox/hq ADR 0594 §2).
|
||||
if o.Action != "unchanged" && o.Action != "held" && o.Action != "waiting" && o.Action != heldStill &&
|
||||
o.Action != waitsBusy {
|
||||
o.Action != waitsBusy && o.Action != keptAsItRuns {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -321,6 +322,11 @@ type Scope struct {
|
||||
// in its own composition has no want, so Placed does not name it (issue 774, review of the re-land, round 1: nats on
|
||||
// the bus node has none, and its nats.conf was guessed as nats's and removed). Nil guards by Placed alone.
|
||||
RemovedByWant map[string]bool
|
||||
// Keep are the resources, by id, the apply leaves as they run when they exist (novox/hq ADR 0594 §2): the bus
|
||||
// server's container and what restarts it, on a want of the bus module without the warrant's mark. A kept resource
|
||||
// that exists is neither recreated nor rewritten and is recorded as the module's with what it was last recorded
|
||||
// reading; one that does not exist is made. Nil keeps nothing.
|
||||
Keep map[string]bool
|
||||
}
|
||||
|
||||
// placedOwner is the module a want still places on the node that the record belongs to, other than the scope's own:
|
||||
@@ -1065,7 +1071,11 @@ func applyScoped(
|
||||
if from, handed := handedFiles[resource.Identity()]; handed && was.ID == "" {
|
||||
previous.Wrote = from.Wrote
|
||||
}
|
||||
outcome, err = applyOne(ctx, sys, resource, run, changed, in, previous, unseal, keepFound)
|
||||
if o, kept := keepAsItRuns(ctx, scope, resource, run, previous); kept {
|
||||
outcome = o
|
||||
} else {
|
||||
outcome, err = applyOne(ctx, sys, resource, run, changed, in, previous, unseal, keepFound)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
// **What was found is kept whatever the apply then did** (novox/hq ADR 0118). The
|
||||
@@ -1272,7 +1282,7 @@ func applyScoped(
|
||||
if outcome.busy != nil {
|
||||
report.Busy = append(report.Busy, *outcome.busy)
|
||||
}
|
||||
if outcome.Action == heldStill || outcome.Action == waitsBusy {
|
||||
if outcome.Action == heldStill || outcome.Action == waitsBusy || outcome.Action == keptAsItRuns {
|
||||
// Not changed: nothing about it moved, so nothing that restarts on it restarts.
|
||||
log(fmt.Sprintf(" %s %s (%s): %s", outcome.Action, outcome.ID, outcome.Target, outcome.Detail))
|
||||
} else if moved(outcome) {
|
||||
@@ -1429,7 +1439,8 @@ func recordApplied(known *store.State, origin string, resource declaration.Resou
|
||||
// moved reports whether applying a resource moved it, so what restarts or reloads on it does: anything but
|
||||
// unchanged, and not a container held still or waiting for its busy one to say idle.
|
||||
func moved(outcome Outcome) bool {
|
||||
return outcome.Action != "unchanged" && outcome.Action != heldStill && outcome.Action != waitsBusy
|
||||
return outcome.Action != "unchanged" && outcome.Action != heldStill && outcome.Action != waitsBusy &&
|
||||
outcome.Action != keptAsItRuns
|
||||
}
|
||||
|
||||
// afterWhatTheyRead is the resources in declared order, except that **a service comes after every
|
||||
@@ -3019,6 +3030,55 @@ func applyNetwork(ctx context.Context, r *declaration.Network, run Runner) (Outc
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// keptAsItRuns is the action of a resource the scope keeps (Scope.Keep, novox/hq ADR 0594 §2), found on the machine
|
||||
// and left as it runs: the bus server on a want of the bus module without the warrant's mark. Its own word, beside
|
||||
// heldStill: nothing about it moved, so nothing that restarts on it restarts, and a person reading the apply sees why
|
||||
// a changed server was not replaced.
|
||||
const keptAsItRuns = "kept-as-it-runs"
|
||||
|
||||
// keepAsItRuns is the outcome of a resource the scope keeps that exists on the machine, true; false for any other,
|
||||
// which is applied as always. A container exists when the runtime knows it by its name; an archive when it was
|
||||
// unpacked at its path before. The outcome carries what the resource was last recorded reading and holding, so its
|
||||
// record is the module's from here on (taken over from the node's own composition at the bus module's first want) and
|
||||
// a later warranted apply still sees what changed under it.
|
||||
func keepAsItRuns(ctx context.Context, scope *Scope, r declaration.Resource, run Runner, previous store.Applied) (Outcome, bool) {
|
||||
if scope == nil || !scope.Keep[r.Identity()] {
|
||||
return Outcome{}, false
|
||||
}
|
||||
out := begin(r)
|
||||
out.Action = keptAsItRuns
|
||||
why := "the bus server is left as it runs: only a want carrying the operator's warrant replaces it (novox/hq ADR 0594 §2)"
|
||||
switch res := r.(type) {
|
||||
case *declaration.Container:
|
||||
if res.Schedule != "" || res.RunOnce {
|
||||
return Outcome{}, false
|
||||
}
|
||||
if _, err := containerState(ctx, res.Name, run); err != nil {
|
||||
return Outcome{}, false
|
||||
}
|
||||
out.reads = previous.Reads
|
||||
out.busySince = previous.BusySince
|
||||
out.Detail = why
|
||||
return out, true
|
||||
case *declaration.Archive:
|
||||
if previous.Wrote == "" || previous.Target != res.Path {
|
||||
return Outcome{}, false
|
||||
}
|
||||
if _, err := os.Stat(res.Path); err != nil {
|
||||
return Outcome{}, false
|
||||
}
|
||||
out.wrote = previous.Wrote
|
||||
out.unpacked = previous.Unpacked
|
||||
if previous.Wrote != res.Digest {
|
||||
out.Detail = why + "; the declared " + res.Digest + " waits for the warrant"
|
||||
} else {
|
||||
out.Detail = why
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
return Outcome{}, false
|
||||
}
|
||||
|
||||
// heldStill is the action of a container left alone because a maintenance window holds it stopped
|
||||
// (novox/hq issue 224). Its own word rather than "unchanged": a person reading a machine that looks
|
||||
// half-stopped needs to see why, and "unchanged" beside a stopped server says the opposite.
|
||||
|
||||
@@ -2,6 +2,8 @@ package apply
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -219,6 +221,82 @@ func TestAResourceThatMovedToItsModulesCompositionIsLeftByTheNodesOwnApply(t *te
|
||||
}
|
||||
})
|
||||
}
|
||||
// Issue 774, ADR 0594 §2: the bus module's first own apply takes over the server the node's own composition made
|
||||
// while the bus was folded. Kept (a want without the warrant's mark), the server's container and its program archive
|
||||
// are left as they run, whatever the build changed in them, and become nats's; its files and the tools' account file
|
||||
// are written. Not kept, the same apply replaces the container.
|
||||
for _, keep := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("the bus server kept %v", keep), func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
conf, account, program := filepath.Join(dir, "nats.conf"), filepath.Join(dir, "tools", "account"), filepath.Join(dir, "program")
|
||||
bus := func(env, confBody, programBody, module string) *declaration.Declaration {
|
||||
archive, _ := archiveDecl(t, "nats.program-snapshot", program, map[string]string{"snapshot": programBody}, "")
|
||||
if module != "" {
|
||||
archive = strings.Replace(archive, `"id":"nats.program-snapshot"`, `"id":"nats.program-snapshot","module":"nats"`, 1)
|
||||
module = `,"module":"nats"`
|
||||
}
|
||||
return declare(t, archive+`,
|
||||
{"id":"nats.server-conf","type":"file","path":"`+conf+`","content":"`+confBody+`"`+module+`},
|
||||
{"id":"nats.server","type":"container","name":"mesh-broker-nats","image":"`+pinned+`","env":{"V":"`+env+
|
||||
`"},"restart-on":["nats.program-snapshot","nats.server-conf"]`+module+`}`)
|
||||
}
|
||||
m := &machine{containers: map[string]*fakeContainer{}}
|
||||
_, state, err := ApplyModule(context.Background(), archHost(t), bus("1", "a", "one", ""), Scope{Module: store.NodeModule},
|
||||
state0(), store.OriginDeclared, m.run, nil, nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
made := m.containers["mesh-broker-nats"]
|
||||
if made == nil {
|
||||
t.Fatalf("the node's own made no server: %v", m.asked)
|
||||
}
|
||||
m.asked = nil
|
||||
next := bus("2", "b", "two", "nats")
|
||||
next.Resources = append(next.Resources, declare(t, `{"id":"nats.tools-account","type":"file","path":"`+account+
|
||||
`","content":"tools","module":"nats"}`).Resources...)
|
||||
scope := Scope{Module: "nats", Placed: map[string]bool{"nats": true}}
|
||||
if keep {
|
||||
scope.Keep = map[string]bool{"nats.server": true, "nats.program-snapshot": true}
|
||||
}
|
||||
report, after, err := ApplyModule(context.Background(), archHost(t), next, scope, state,
|
||||
store.OriginDeclared, m.run, nil, nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var moved []string
|
||||
for _, a := range m.asked {
|
||||
for _, verb := range []string{"docker rm", "docker run", "docker create", "docker stop", "docker restart"} {
|
||||
if strings.HasPrefix(a, verb+" ") {
|
||||
moved = append(moved, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
if keep == (len(moved) > 0) {
|
||||
t.Fatalf("kept %v, and the server was moved by %v", keep, moved)
|
||||
}
|
||||
if got, _ := os.ReadFile(conf); string(got) != "b" {
|
||||
t.Errorf("the server's file holds %q", got)
|
||||
}
|
||||
if got, _ := os.ReadFile(account); string(got) != "tools" {
|
||||
t.Errorf("the tools' account file holds %q", got)
|
||||
}
|
||||
if got, _ := os.ReadFile(filepath.Join(program, "snapshot")); keep != (string(got) == "one") {
|
||||
t.Errorf("kept %v, and the server's program is %q", keep, got)
|
||||
}
|
||||
for _, id := range []string{"nats.server", "nats.program-snapshot"} {
|
||||
rec, ok := after.Find(id)
|
||||
if !ok || rec.Module != "nats" {
|
||||
t.Errorf("%s is recorded %+v, not as nats's", id, rec)
|
||||
}
|
||||
if o := outcomeOf(report, id); keep != (o.Action == keptAsItRuns) {
|
||||
t.Errorf("kept %v, and %s was %s: %s", keep, id, o.Action, o.Detail)
|
||||
}
|
||||
}
|
||||
if keep && report.Changed() == false {
|
||||
t.Error("an apply that wrote the server's files says nothing changed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func state0() store.State { return store.State{} }
|
||||
|
||||
Reference in New Issue
Block a user