The node agent keeps the bus server unless the want carries the warrant's mark, and the bus's have says what the server runs (ADR 0594 §2-§4, §6)
This commit is contained in:
@@ -85,7 +85,7 @@ func (h sessionHaves) Read(ctx context.Context, module string) (agent.Have, bool
|
||||
return agent.Have{}, false
|
||||
}
|
||||
return agent.Have{Module: module, FP: got.FP, Composed: got.Composed, Phase: string(got.Phase), Detail: got.Detail,
|
||||
Sealed: got.SealedAt}, true
|
||||
Sealed: got.SealedAt, Server: got.Server, Build: got.Build}, true
|
||||
}
|
||||
|
||||
// ReadErr is Read telling a have absent from one the bus could not read (agent.HavesReadErr).
|
||||
@@ -103,7 +103,7 @@ func (h sessionHaves) ReadErr(ctx context.Context, module string) (agent.Have, b
|
||||
return agent.Have{}, false, err
|
||||
}
|
||||
return agent.Have{Module: module, FP: got.FP, Composed: got.Composed, Phase: string(got.Phase), Detail: got.Detail,
|
||||
Sealed: got.SealedAt}, true, nil
|
||||
Sealed: got.SealedAt, Server: got.Server, Build: got.Build}, true, nil
|
||||
}
|
||||
|
||||
func (h sessionHaves) Write(ctx context.Context, x agent.Have) {
|
||||
@@ -120,7 +120,7 @@ func (h sessionHaves) Write(ctx context.Context, x agent.Have) {
|
||||
}
|
||||
h.session.NoteModule(ctx, link.ModuleFact{Module: x.Module, FP: x.FP, Composed: x.Composed, Phase: phase,
|
||||
Failed: phase == string(delivery.PhaseFailed), Detail: shorten(x.Detail), Judgement: x.Judgement, Waits: x.Waits,
|
||||
Sealed: x.Sealed})
|
||||
Sealed: x.Sealed, Server: x.Server})
|
||||
}
|
||||
|
||||
func (h sessionHaves) Removed(ctx context.Context, module string) {
|
||||
@@ -538,7 +538,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
Say: say,
|
||||
// A failure of the bus not answering for a moment is tried again: 2 s, 5 s, 15 s, 60 s, then every 5 min.
|
||||
Backoff: link.Backoff,
|
||||
Apply: func(ctx context.Context, c delivery.Composed, others []delivery.Composed) agent.Result {
|
||||
Apply: func(ctx context.Context, c delivery.Composed, others []delivery.Composed, keep map[string]bool) agent.Result {
|
||||
actx, done := applyContext(session.WithLink(ctx))
|
||||
// While the grants part's list is followed, the controller's list in the bus module's composition is not
|
||||
// written: the apply keeps the list on the machine (ADR 0547 clause 8, never both).
|
||||
@@ -547,7 +547,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
if herr != nil {
|
||||
res = failedWith(herr.Error())
|
||||
} else {
|
||||
res = applyComposition(actx, opts, held, others, ag.Placed(scopeName(c)), ag.RemovedByWant(), sched, say)
|
||||
res = applyComposition(actx, opts, held, others, ag.Placed(scopeName(c)), ag.RemovedByWant(), keep, sched, say)
|
||||
}
|
||||
done()
|
||||
// The mesh's own certificate it carries is kept for this machine's record (ADR 0555 §3).
|
||||
@@ -632,6 +632,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
composerNow.Store(composer) // what the verb compose asks a pass of, this run's alone
|
||||
env.Composed, env.SelfComposed, env.Policy = composer, true, composer.Policy
|
||||
env.HoldsData = composer.HoldsData // what runs from the kept declaration says Data as its manifest does
|
||||
env.BusServer = composer.BusServer // the bus server an unwarranted want leaves as it runs (ADR 0594 §2)
|
||||
session.NoteComposes(linked, delivery.ComposesOwn, "it composes its own modules, and applies no composition made elsewhere")
|
||||
ag = agent.New(env)
|
||||
own.set(ag)
|
||||
@@ -692,7 +693,7 @@ func (c *agentNow) get() *agent.Agent {
|
||||
//
|
||||
// removed is the modules whose want says removed: in the node's own apply, a resource of any other module goes only when
|
||||
// its module is among them (issue 774).
|
||||
func applyComposition(ctx context.Context, opts options, c delivery.Composed, others []delivery.Composed, placed, removed map[string]bool,
|
||||
func applyComposition(ctx context.Context, opts options, c delivery.Composed, others []delivery.Composed, placed, removed, keep map[string]bool,
|
||||
sched *apply.Scheduler, say link.Announce) agent.Result {
|
||||
name := scopeName(c)
|
||||
if c.Module == "" {
|
||||
@@ -708,7 +709,7 @@ func applyComposition(ctx context.Context, opts options, c delivery.Composed, ot
|
||||
if err := refuseEmptyWrites(d); err != nil {
|
||||
return failedWith(err.Error())
|
||||
}
|
||||
scope := apply.Scope{Module: name, Others: resourcesOf(others), Placed: placed}
|
||||
scope := apply.Scope{Module: name, Others: resourcesOf(others), Placed: placed, Keep: keep}
|
||||
if name == agent.NodeModule {
|
||||
scope.RemovedByWant = removed
|
||||
if scope.RemovedByWant == nil {
|
||||
|
||||
+162
-7
@@ -62,6 +62,13 @@ type Have struct {
|
||||
// slot (delivery.Have.SealedAt): kept in the have so the composer reuses them after a restart (ADR 0547 §3), each at
|
||||
// its own slot alone (issue 1126, C1).
|
||||
Sealed delivery.Seals
|
||||
// Server is, in the bus module's have, what the bus server runs (delivery.Have.Server, novox/hq ADR 0594 §4): the
|
||||
// fingerprint whose server this node agent last made, or the old-form build of the server it took over. Empty on
|
||||
// every other have.
|
||||
Server string
|
||||
// Build is the old-form build a have from before the fingerprints names (delivery.Have.Build): read, never written
|
||||
// by the agent; the server the bus module's first want takes over runs it.
|
||||
Build string
|
||||
}
|
||||
|
||||
// Haves is the node's have bucket as the agent uses it.
|
||||
@@ -103,8 +110,13 @@ type Env struct {
|
||||
Haves Haves
|
||||
// Apply applies one module's composition (scope NodeModule for the node's own), given the compositions the node's
|
||||
// other modules run so a path, unit or group they hold is not removed or taken. It is called with one module at a
|
||||
// time.
|
||||
Apply func(ctx context.Context, c delivery.Composed, others []delivery.Composed) Result
|
||||
// time. keep is the resources, by id, the apply leaves as they run when they exist (apply.Scope.Keep): the bus
|
||||
// server's, on a want of the bus module without the warrant's mark (novox/hq ADR 0594 §2); nil keeps nothing.
|
||||
Apply func(ctx context.Context, c delivery.Composed, others []delivery.Composed, keep map[string]bool) Result
|
||||
// BusServer is, for a module that provides the mesh's bus, the ids its manifest declares of the bus server's
|
||||
// resources (catalogue.BusServerOf, novox/hq ADR 0594 §7), empty for any other module; known says whether the
|
||||
// module's manifest could be read. Nil names no bus module.
|
||||
BusServer func(ctx context.Context, module string) (ids []string, known bool)
|
||||
// Remove removes the module's own resources and nothing of another's.
|
||||
Remove func(ctx context.Context, module string, others []delivery.Composed) Result
|
||||
// Backup backs a module up before a new composition of it is applied; nil backs up nothing.
|
||||
@@ -221,6 +233,9 @@ type Agent struct {
|
||||
again chan againAsk
|
||||
transient chan struct{}
|
||||
stopped chan struct{}
|
||||
// server is, by bus module, what its have says the bus server runs (Have.Server): read once from the have, then
|
||||
// kept as this agent writes it, so every have written of the module carries it (novox/hq ADR 0594 §4).
|
||||
server map[string]string
|
||||
}
|
||||
|
||||
type againAsk struct {
|
||||
@@ -261,7 +276,7 @@ func New(env Env) *Agent {
|
||||
return &Agent{env: env, wants: map[string]wantNow{}, comps: map[string]compNow{}, running: map[string]delivery.Have{}, applied: map[string]compNow{}, had: map[string]compNow{}, keptData: map[string]bool{},
|
||||
attempt: map[string]attempt{}, checked: map[string]bool{}, prior: map[string]bool{}, said: map[string]string{}, notComposed: map[string]notComposedMark{},
|
||||
retry: make(chan []string, 8), reconcile: make(chan []string, 64), failedAgain: make(chan string, 8), tries: map[string]int{}, again: make(chan againAsk, 64),
|
||||
transient: make(chan struct{}, 1), stopped: make(chan struct{})}
|
||||
transient: make(chan struct{}, 1), stopped: make(chan struct{}), server: map[string]string{}}
|
||||
}
|
||||
|
||||
// Run follows the wants until ctx ends. The keys there when it starts are taken first, the node's own composition
|
||||
@@ -572,6 +587,13 @@ func (a *Agent) NotComposedEach(ctx context.Context, why string, held map[string
|
||||
// said runs, its fingerprint, digest and sealed values, rather than a have naming nothing: what runs is not lost, and
|
||||
// the boxes the composer reuses after a restart stay (review of L2, rounds 2 and 3).
|
||||
func (a *Agent) writeHave(ctx context.Context, h Have) {
|
||||
if h.Server == "" && h.Module != NodeModule {
|
||||
h.Server = a.serverOf(ctx, h.Module)
|
||||
} else if h.Server != "" {
|
||||
a.mu.Lock()
|
||||
a.server[h.Module] = h.Server
|
||||
a.mu.Unlock()
|
||||
}
|
||||
if h.Composed == "" && h.Module != NodeModule {
|
||||
if last, ok := a.env.Haves.Read(ctx, h.Module); ok && last.Composed != "" {
|
||||
h.FP, h.Composed = last.FP, last.Composed
|
||||
@@ -588,6 +610,56 @@ func (a *Agent) writeHave(ctx context.Context, h Have) {
|
||||
a.env.Haves.Write(ctx, h)
|
||||
}
|
||||
|
||||
// serverOf is what the module's have says the bus server runs (Have.Server): kept since this agent read or wrote it,
|
||||
// else read from the have once. Empty for every module but the bus.
|
||||
func (a *Agent) serverOf(ctx context.Context, module string) string {
|
||||
a.mu.Lock()
|
||||
s, known := a.server[module]
|
||||
a.mu.Unlock()
|
||||
if known {
|
||||
return s
|
||||
}
|
||||
if h, ok := a.env.Haves.Read(ctx, module); ok {
|
||||
s = h.Server
|
||||
}
|
||||
a.mu.Lock()
|
||||
if _, written := a.server[module]; !written {
|
||||
a.server[module] = s
|
||||
}
|
||||
s = a.server[module]
|
||||
a.mu.Unlock()
|
||||
return s
|
||||
}
|
||||
|
||||
// busKeep is, for a module that provides the mesh's bus, the ids of its server's resources (novox/hq ADR 0594 §7) as
|
||||
// its composition names them (`<module>.<id>`), and true; nil and true for any other module. known is false when the
|
||||
// module's manifest could not be read: whether it is the bus cannot be told, so nothing of it is applied.
|
||||
func (a *Agent) busKeep(ctx context.Context, module string) (server map[string]bool, known bool) {
|
||||
if module == NodeModule || a.env.BusServer == nil {
|
||||
return nil, true
|
||||
}
|
||||
ids, known := a.env.BusServer(ctx, module)
|
||||
if !known || len(ids) == 0 {
|
||||
return nil, known
|
||||
}
|
||||
server = map[string]bool{}
|
||||
for _, id := range ids {
|
||||
server[module+"."+id] = true
|
||||
}
|
||||
return server, true
|
||||
}
|
||||
|
||||
// serverOwed says a want of the bus module carries the warrant's mark at a fingerprint whose server does not run yet
|
||||
// (novox/hq ADR 0594 §4): applied once more though its fingerprint and composition are what runs, so the server is
|
||||
// replaced at the fingerprint already applied.
|
||||
func (a *Agent) serverOwed(ctx context.Context, module string, w wantNow) bool {
|
||||
if w.Want.Warrant == "" {
|
||||
return false
|
||||
}
|
||||
server, _ := a.busKeep(ctx, module)
|
||||
return len(server) > 0 && a.serverOf(ctx, module) != w.Want.FP
|
||||
}
|
||||
|
||||
func (a *Agent) sealedOf(ctx context.Context, module, fp, composed string) delivery.Seals {
|
||||
a.mu.Lock()
|
||||
if c, ok := a.applied[module]; ok && c.Err == nil && c.Dig == composed {
|
||||
@@ -1213,6 +1285,16 @@ func (a *Agent) settleOne(ctx context.Context, module string) {
|
||||
a.remove(ctx, module, w)
|
||||
return
|
||||
}
|
||||
if _, known := a.busKeep(ctx, module); !known {
|
||||
// Whether it is the bus, whose server an unwarranted want never replaces (novox/hq ADR 0594 §2), cannot be told:
|
||||
// nothing of it is applied until its manifest reads.
|
||||
a.mu.Lock()
|
||||
a.sayOnce("bus-unknown:"+module, fmt.Sprintf("%s at %s is not applied: its manifest cannot be read, so whether it "+
|
||||
"is the bus, whose server only a warranted want replaces, cannot be told", module, w.Want.FP))
|
||||
a.mu.Unlock()
|
||||
return
|
||||
}
|
||||
owed := a.serverOwed(ctx, module, w)
|
||||
a.mu.Lock()
|
||||
comp, have := a.compositionFor(module, w.Want.FP)
|
||||
a.mu.Unlock()
|
||||
@@ -1247,14 +1329,15 @@ func (a *Agent) settleOne(ctx context.Context, module string) {
|
||||
if !a.checked[module] {
|
||||
a.checked[module] = true
|
||||
a.mu.Unlock()
|
||||
if a.baseline(ctx, module, w, comp) {
|
||||
if !owed && a.baseline(ctx, module, w, comp) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
a.mu.Unlock()
|
||||
}
|
||||
a.mu.Lock()
|
||||
if r, ok := a.running[module]; ok && r.FP == w.Want.FP && r.Composed == w.Want.Composed && r.Phase != delivery.PhaseRemoved {
|
||||
// What runs is what the want names, and no warranted want owes the bus server's replacement (ADR 0594 §4).
|
||||
if r, ok := a.running[module]; ok && !owed && r.FP == w.Want.FP && r.Composed == w.Want.Composed && r.Phase != delivery.PhaseRemoved {
|
||||
a.mu.Unlock()
|
||||
return
|
||||
}
|
||||
@@ -1405,6 +1488,35 @@ func (a *Agent) inactiveAtStart(ctx context.Context, module string, comp compNow
|
||||
|
||||
func (a *Agent) apply(ctx context.Context, module string, w wantNow, comp compNow, others []delivery.Composed) {
|
||||
fp, dig := w.Want.FP, w.Want.Composed
|
||||
// The bus module's server is kept as it runs unless the want carries the warrant's mark (novox/hq ADR 0594 §2, §6):
|
||||
// the mark alone decides. Its have says what the server runs (§4): before the apply what it ran, the fingerprint
|
||||
// applied when the apply kept it a fingerprint or a build ago, and the want's fingerprint once a warranted apply
|
||||
// replaced it.
|
||||
keep, _ := a.busKeep(ctx, module)
|
||||
ranServer, madeServer := "", ""
|
||||
if len(keep) > 0 {
|
||||
ranServer = a.serverOf(ctx, module)
|
||||
if ranServer == "" {
|
||||
if h, ok := a.env.Haves.Read(ctx, module); ok {
|
||||
ranServer = h.FP
|
||||
if ranServer == "" {
|
||||
ranServer = h.Build
|
||||
}
|
||||
}
|
||||
}
|
||||
if ranServer == "" {
|
||||
ranServer = fp // a bus module this node never ran: its server is made at this fingerprint
|
||||
}
|
||||
madeServer = ranServer
|
||||
a.mu.Lock()
|
||||
a.server[module] = ranServer
|
||||
a.mu.Unlock()
|
||||
if w.Want.Warrant != "" {
|
||||
keep, madeServer = nil, fp
|
||||
a.env.Say(fmt.Sprintf("%s at %s carries the warrant %q: its server, running %s, is replaced", module, fp,
|
||||
w.Want.Warrant, ranServer))
|
||||
}
|
||||
}
|
||||
a.mu.Lock()
|
||||
_, ranBefore := a.running[module]
|
||||
ranBefore = ranBefore || a.prior[module]
|
||||
@@ -1444,7 +1556,7 @@ func (a *Agent) apply(ctx context.Context, module string, w wantNow, comp compNo
|
||||
}
|
||||
}
|
||||
a.writeHave(ctx, Have{Module: module, FP: fp, Composed: dig, Phase: string(delivery.PhaseApplying)})
|
||||
res := a.env.Apply(ctx, comp.C, others)
|
||||
res := a.env.Apply(ctx, comp.C, others, keep)
|
||||
a.mu.Lock()
|
||||
what := fmt.Sprintf("%s at %s", module, fp)
|
||||
if module == NodeModule {
|
||||
@@ -1494,7 +1606,8 @@ func (a *Agent) apply(ctx context.Context, module string, w wantNow, comp compNo
|
||||
if a.env.Settled != nil {
|
||||
a.env.Settled(module)
|
||||
}
|
||||
a.writeHave(ctx, Have{Module: module, FP: fp, Composed: dig, Phase: string(delivery.PhaseRunning), Detail: res.Detail})
|
||||
a.writeHave(ctx, Have{Module: module, FP: fp, Composed: dig, Phase: string(delivery.PhaseRunning), Detail: res.Detail,
|
||||
Server: madeServer})
|
||||
}
|
||||
|
||||
func (a *Agent) remove(ctx context.Context, module string, w wantNow) {
|
||||
@@ -1599,6 +1712,13 @@ func (a *Agent) waitsForYes(ctx context.Context, module string, w wantNow, comp
|
||||
if module == NodeModule || a.env.Policy == nil || a.env.Policy(ctx, module) != catalogue.PolicyRecord || w.Want.Yes == comp.Dig {
|
||||
return false
|
||||
}
|
||||
// For the bus module the judgement covers its server alone (novox/hq ADR 0594 §3): a want with the warrant's mark is
|
||||
// the operator's warrant, the put's ask after the streams' snapshot or the hand write at the terminal, and stands as
|
||||
// its yes (ADR 0546 §5); one without it is judged with the server's resources as they run, which it leaves.
|
||||
server, _ := a.busKeep(ctx, module)
|
||||
if len(server) > 0 && w.Want.Warrant != "" {
|
||||
return false
|
||||
}
|
||||
a.readKeptData(ctx, module) // the manifest may read now though it did not at the start
|
||||
a.mu.Lock()
|
||||
r, ran := a.running[module]
|
||||
@@ -1632,6 +1752,11 @@ func (a *Agent) waitsForYes(ctx context.Context, module string, w wantNow, comp
|
||||
}
|
||||
var j judge.Judged
|
||||
switch {
|
||||
case len(server) > 0 && !known:
|
||||
// What the bus runs cannot be read, and its server is left as it runs whatever it is: nothing to hold.
|
||||
return false
|
||||
case len(server) > 0:
|
||||
j = judge.JudgeMove(&from, withServerAsItRuns(from, comp.C, server))
|
||||
case known:
|
||||
j = judge.JudgeMove(&from, comp.C)
|
||||
case prior:
|
||||
@@ -1653,6 +1778,36 @@ func (a *Agent) waitsForYes(ctx context.Context, module string, w wantNow, comp
|
||||
return true
|
||||
}
|
||||
|
||||
// withServerAsItRuns is c with each resource of server taken as from runs it (novox/hq ADR 0594 §3): what an apply that
|
||||
// keeps the bus server makes of the machine, so its judgement covers the rest of the module alone. A server resource
|
||||
// from does not run stays as c declares it: an apply makes one that does not exist.
|
||||
func withServerAsItRuns(from, c delivery.Composed, server map[string]bool) delivery.Composed {
|
||||
running := map[string]json.RawMessage{}
|
||||
for _, raw := range from.Resources {
|
||||
if id := resourceID(raw); server[id] {
|
||||
running[id] = raw
|
||||
}
|
||||
}
|
||||
out := c
|
||||
out.Resources = make([]json.RawMessage, 0, len(c.Resources))
|
||||
for _, raw := range c.Resources {
|
||||
if was, ok := running[resourceID(raw)]; ok {
|
||||
raw = was
|
||||
}
|
||||
out.Resources = append(out.Resources, raw)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// resourceID is a composed resource's id, empty when it names none.
|
||||
func resourceID(raw json.RawMessage) string {
|
||||
var r struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
_ = json.Unmarshal(raw, &r)
|
||||
return r.ID
|
||||
}
|
||||
|
||||
// runningComposition is what the module runs now: the composition last applied, else its resources in the declaration
|
||||
// the node kept from before the switch. Called with a.mu held.
|
||||
//
|
||||
|
||||
@@ -17,6 +17,15 @@ import (
|
||||
|
||||
const node = "n1"
|
||||
|
||||
func mustComposedKey(t *testing.T, module, fp string) string {
|
||||
t.Helper()
|
||||
key, err := delivery.ComposedKey(node, module, fp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func fpOf(c string) string { return delivery.SourcePrefix2 + strings.Repeat(c, 64) }
|
||||
|
||||
// A watcher the test writes to; it says Caught once the keys written before the watch began are out.
|
||||
@@ -117,6 +126,7 @@ type bed struct {
|
||||
backup func(module string) Backup
|
||||
kept map[string][]json.RawMessage
|
||||
others [][]delivery.Composed
|
||||
keeps []map[string]bool // what each apply was told to keep as it runs (ADR 0594 §2)
|
||||
said []string
|
||||
// settled is each module Settled was told, and settledRan whether what runs held it then.
|
||||
settled []string
|
||||
@@ -128,10 +138,11 @@ type bed struct {
|
||||
func newBed(t *testing.T) *bed {
|
||||
b := &bed{t: t, wants: newFeed(), comps: newFeed(), haves: &haves{}}
|
||||
b.env = Env{Node: node, Wants: b.wants, Composed: b.comps, Haves: b.haves,
|
||||
Apply: func(_ context.Context, c delivery.Composed, others []delivery.Composed) Result {
|
||||
Apply: func(_ context.Context, c delivery.Composed, others []delivery.Composed, keep map[string]bool) Result {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.others = append(b.others, others)
|
||||
b.keeps = append(b.keeps, keep)
|
||||
name := c.Module
|
||||
if name == "" {
|
||||
name = NodeModule
|
||||
@@ -1529,6 +1540,115 @@ func TestTheNodesOwnCompositionIsAppliedWhenItDiffersAndARecordMoveWaitsForAYes(
|
||||
return len(b.appliedList()) == applied+2 && h.Phase == string(delivery.PhaseRunning) && h.FP == fpB &&
|
||||
r.Phase == string(delivery.PhaseRunning) && r.FP == fpB && r.Composed == rdig
|
||||
})
|
||||
// Issue 774, ADR 0594 §2-§4: the bus module's server is kept as it runs on every want without the warrant's mark (a
|
||||
// new fingerprint whose server recreates, a revision of its user list), never held for a yes, and its have names the
|
||||
// fingerprint applied and, beside it, what the server runs: at the take-over the old-form build. A warranted want at
|
||||
// the fingerprint already applied replaces the server once (the warrant is its yes), and the same want again applies
|
||||
// nothing. A module that is not the bus keeps nothing, marked or not.
|
||||
t.Run("the bus module", func(t *testing.T) {
|
||||
b := newBed(t)
|
||||
b.env.SelfComposed = true
|
||||
b.env.Policy = func(context.Context, string) string { return catalogue.PolicyRecord }
|
||||
b.env.BusServer = func(_ context.Context, m string) ([]string, bool) {
|
||||
if m == "nats" {
|
||||
return []string{"program-entrypoint", "program-snapshot", "server"}, true
|
||||
}
|
||||
return nil, true
|
||||
}
|
||||
const took = "build-1792674595610176744"
|
||||
b.haves.Write(context.Background(), Have{Module: "nats", Build: took, Phase: string(delivery.PhaseRunning)})
|
||||
stop := b.run()
|
||||
defer stop()
|
||||
bus := func(fp, image, users string) (string, []byte, string) {
|
||||
server, _ := json.Marshal(map[string]any{"id": "nats.server", "type": "container", "module": "nats",
|
||||
"name": "mesh-broker-nats", "image": "nats@sha256:" + strings.Repeat(image, 64)})
|
||||
c := delivery.Composed{Node: node, Module: "nats", FP: fp, Resources: []json.RawMessage{server,
|
||||
file("nats", "server-conf", "listen"), file("nats", "bus-users", users)}}
|
||||
key, _ := delivery.ComposedKey(node, "nats", fp)
|
||||
return encodedAs(t, c, key)
|
||||
}
|
||||
want := func(module, fp, warrant string) {
|
||||
key, _ := delivery.WantKey(node, module)
|
||||
raw, err := delivery.EncodeWant(delivery.Want{FP: fp, Self: true, Warrant: warrant})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.wants.put(key, raw)
|
||||
}
|
||||
server := map[string]bool{"nats.server": true, "nats.program-snapshot": true, "nats.program-entrypoint": true}
|
||||
lastKeep := func() map[string]bool {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.keeps[len(b.keeps)-1]
|
||||
}
|
||||
fpA, fpB := fpOf("a"), fpOf("b")
|
||||
for i, row := range []struct {
|
||||
name, fp, image, users, warrant string
|
||||
applies bool
|
||||
keep map[string]bool
|
||||
server string
|
||||
}{
|
||||
{name: "the take-over of the folded server", fp: fpA, image: "1", users: "u1", applies: true, keep: server, server: took},
|
||||
{name: "a new fingerprint whose server recreates", fp: fpB, image: "2", users: "u1", applies: true, keep: server, server: took},
|
||||
{name: "a revision of the user list", fp: fpB, image: "2", users: "u2", applies: true, keep: server, server: took},
|
||||
{name: "the operator's warrant at the fingerprint applied", fp: fpB, image: "2", users: "u2", warrant: delivery.WarrantOperator,
|
||||
applies: true, server: fpB},
|
||||
{name: "the same warrant again", fp: fpB, image: "2", users: "u2", warrant: delivery.WarrantOperator, server: fpB},
|
||||
{name: "a build after the warrant", fp: fpA, image: "3", users: "u2", applies: true, keep: server, server: fpB},
|
||||
} {
|
||||
before := len(b.appliedList())
|
||||
key, raw, dig := bus(row.fp, row.image, row.users)
|
||||
b.comps.put(key, raw)
|
||||
want("nats", row.fp, row.warrant)
|
||||
if row.applies {
|
||||
eventually(t, row.name+": not applied", func() bool {
|
||||
h := b.haves.last("nats")
|
||||
return len(b.appliedList()) == before+1 && h.Composed == dig && h.Phase == string(delivery.PhaseRunning)
|
||||
})
|
||||
if got := lastKeep(); !maps.Equal(got, row.keep) {
|
||||
t.Errorf("%s: the apply kept %v, want %v", row.name, got, row.keep)
|
||||
}
|
||||
} else {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if got := b.appliedList(); len(got) != before {
|
||||
t.Errorf("%s: applied again: %v", row.name, got)
|
||||
}
|
||||
}
|
||||
if h := b.haves.last("nats"); h.FP != row.fp || h.Server != row.server || h.Waits != "" {
|
||||
t.Errorf("row %d, %s: the bus's have is %+v, its server %s", i, row.name, h, row.server)
|
||||
}
|
||||
}
|
||||
// Every have written of the bus says what its server runs, applying included: one that did not would lose it.
|
||||
b.haves.mu.Lock()
|
||||
for _, h := range b.haves.log[1:] {
|
||||
if h.Module == "nats" && h.Server == "" {
|
||||
t.Errorf("a have of the bus names no server: %+v", h)
|
||||
}
|
||||
}
|
||||
b.haves.mu.Unlock()
|
||||
for _, warrant := range []string{"", delivery.WarrantOperator} {
|
||||
before := len(b.appliedList())
|
||||
fp := fpOf("c")
|
||||
if warrant != "" {
|
||||
fp = fpOf("d")
|
||||
}
|
||||
key, raw, _ := encodedAs(t, delivery.Composed{Node: node, Module: "web", FP: fp,
|
||||
Resources: []json.RawMessage{file("web", "x", warrant)}}, mustComposedKey(t, "web", fp))
|
||||
b.comps.put(key, raw)
|
||||
want("web", fp, warrant)
|
||||
eventually(t, "web was not applied", func() bool { return len(b.appliedList()) == before+1 })
|
||||
if got := lastKeep(); got != nil {
|
||||
t.Errorf("a module that is not the bus kept %v", got)
|
||||
}
|
||||
if h := b.haves.last("web"); h.Server != "" {
|
||||
t.Errorf("web's have names a server: %+v", h)
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if got := b.appliedList(); len(got) != 7 {
|
||||
t.Errorf("applied %v, not once for each change", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 1957/AC1 and AC4 (unit; ADR 0568 §1, §4): an engine start takes a module from what the machine shows, not only from the
|
||||
|
||||
@@ -384,6 +384,7 @@ func (q *Session) writeHaves(ctx context.Context, bus Bus) {
|
||||
h.Phase = delivery.PhaseFailed
|
||||
}
|
||||
h.SealedAt = f.Sealed
|
||||
h.Server = f.Server
|
||||
h.Health = s.healthOf(module, f)
|
||||
if h.Detail == "" {
|
||||
h.Detail = s.wordsOf(module, h.Health)
|
||||
|
||||
@@ -641,4 +641,6 @@ type ModuleFact struct {
|
||||
Waits string
|
||||
// Sealed is the values the composition it names carries sealed to this node's key, by slot (delivery.Have.SealedAt).
|
||||
Sealed delivery.Seals
|
||||
// Server is, for the bus module, what its server runs (delivery.Have.Server, novox/hq ADR 0594 §4).
|
||||
Server string
|
||||
}
|
||||
|
||||
@@ -1163,6 +1163,30 @@ func (c *Composer) Policy(ctx context.Context, module string) string {
|
||||
return policy
|
||||
}
|
||||
|
||||
// BusServer is, for a module whose manifest provides the mesh's bus, the ids it declares of the bus server's resources
|
||||
// (catalogue.BusServerOf, novox/hq ADR 0594 §7), none for any other; known says whether the manifest could be read.
|
||||
func (c *Composer) BusServer(ctx context.Context, module string) (ids []string, known bool) {
|
||||
c.mu.Lock()
|
||||
m, ok := c.modules[module]
|
||||
c.mu.Unlock()
|
||||
if !ok || m.Manifest == "" {
|
||||
return nil, false
|
||||
}
|
||||
raw, err := reader{c: c}.Manifest(ctx, m.Manifest)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
parsed, held, err := c.taken(module, m, raw)
|
||||
if err != nil || held != "" {
|
||||
return nil, false
|
||||
}
|
||||
for id := range catalogue.BusServerOf(parsed) {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Strings(ids)
|
||||
return ids, true
|
||||
}
|
||||
|
||||
// HoldsData says whether a module's manifest declares data (catalogue.Manifest.DataItems), as a composition's Data says
|
||||
// it, and whether the manifest could be read: the agent's Data for what runs of a module from the kept declaration
|
||||
// (agent.Env.HoldsData, issue 924).
|
||||
|
||||
Reference in New Issue
Block a user