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:
2026-11-03 08:56:36 +01:00
parent 089ac78007
commit ffcd8f6d61
2 changed files with 142 additions and 4 deletions
+78
View File
@@ -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{} }