R3-3: the node's own composer has no path without an entry call; its tests compose from the catalog half
Builds: design 47 G §628, §421, A-312, A-317 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -53,6 +53,7 @@ type fakeBus struct {
|
||||
kv map[string][]byte // bucket/key
|
||||
said []string
|
||||
reads [][2]string // bucket, pattern or key: every watch and get
|
||||
half catalogHalf // the catalog half the bus's composers ask the entry of
|
||||
}
|
||||
|
||||
func newFakeBus() *fakeBus {
|
||||
@@ -60,7 +61,7 @@ func newFakeBus() *fakeBus {
|
||||
}
|
||||
|
||||
func (b *fakeBus) env() Env {
|
||||
return Env{Node: "n1",
|
||||
return Env{Node: "n1", Entry: b.half.call(), // the catalog half answers what the test's bus holds of it, nothing until a test sets it
|
||||
Watch: func(_ context.Context, bucket, pattern string) (<-chan agent.Update, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
@@ -163,10 +164,24 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
man, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "lemurs", Manifest: lemurs})
|
||||
key, _ := meshcatalogclient.ManifestsKey(digest)
|
||||
b.kv[buckets.Manifests+"/"+key] = man
|
||||
// The catalog half the node composes from, by fingerprint: the node's own composer asks it and reads nothing of the indexer.
|
||||
half := catalogHalf{}
|
||||
half.add(t, "lemurs", fp, lemurs)
|
||||
oldieFp := meshcatalogclient.SourcePrefix2 + strings.Repeat("c", 64)
|
||||
toolFp := meshcatalogclient.SourcePrefix2 + strings.Repeat("7", 64)
|
||||
keeperFp := meshcatalogclient.SourcePrefix2 + strings.Repeat("9", 64)
|
||||
toolBody := `{"module":"tool","version":"1","capabilities":["systemd"],"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},` +
|
||||
`"resources":[{"id":"service","type":"service","unit":"tool.service","state":"running","boot":"enabled"}]}`
|
||||
half.add(t, "tool", toolFp, toolBody)
|
||||
b.half = half
|
||||
keeperBody := `{"module":"keeper","version":"1","own-secrets":{"password":"/var/lib/mesh/keeper/password"},` +
|
||||
`"resources":[{"id":"state","type":"directory","path":"/var/lib/mesh/keeper","mode":"0700"}]}`
|
||||
half.add(t, "keeper", keeperFp, keeperBody)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
env := b.env()
|
||||
env.Entry = half.call()
|
||||
// The vault's own: keeper's password sealed to n1's key, sealed again (other bytes) at every ask.
|
||||
var asks, generation int
|
||||
sealedAt := map[string]int{} // each box handed, by the generation it opens to
|
||||
@@ -195,26 +210,22 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
// oldie is placed and no want names it (a module at a build of the old form, nats on novox): the node runs it inside
|
||||
// its own composition, and composes it there again, even before the agent has applied anything (Running empty).
|
||||
oldie := `{"module":"oldie","version":"1","capabilities":["systemd"],"resources":[{"id":"service","type":"service","unit":"oldie.service","state":"running","boot":"enabled"}]}`
|
||||
odigest := meshcatalogclient.ManifestDigest([]byte(oldie))
|
||||
oman, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "oldie", Manifest: oldie})
|
||||
okey, _ := meshcatalogclient.ManifestsKey(odigest)
|
||||
b.kv[buckets.Manifests+"/"+okey] = oman
|
||||
omod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: meshcatalogclient.SourcePrefix2 + strings.Repeat("c", 64), Manifest: odigest, Repository: "novox/o", Commit: "c"})
|
||||
half.add(t, "oldie", oldieFp, oldie)
|
||||
place := assignedValues(t, meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"},
|
||||
Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}, {Module: "oldie"}}})
|
||||
have, _ := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}})
|
||||
mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: digest, Repository: "novox/x", Commit: "c"})
|
||||
want, _ := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Fp: fp, Self: true})
|
||||
nodeWant, _ := bucketstest.NodeWant(meshwants.WantsByWantNodeNodeValue{Self: true})
|
||||
b.putAssigned(t, place)
|
||||
b.put(t, buckets.Have, "n1.node", have)
|
||||
oldHave, _ := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Build: "build-1", Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK})
|
||||
b.put(t, buckets.Have, "n1.oldie", oldHave)
|
||||
// The picture of the catalog half is set directly (no indexer is watched): lemurs's entry, which its want names, and oldie,
|
||||
// which the node runs inside its own composition and no want names.
|
||||
c.takeModule(agent.Update{Key: "lemurs", Value: mod})
|
||||
c.takeModule(agent.Update{Key: "oldie", Value: omod})
|
||||
c.holdEntry(t, "lemurs", fp, lemurs)
|
||||
// The catalog half answers lemurs's entry, which its want names, and oldie's, which the node runs inside its own composition and
|
||||
// no want names.
|
||||
c.NoteRunning(nodeengine.Running{Node: "n1", Module: "oldie", Fingerprint: oldieFp})
|
||||
c.RunningRead()
|
||||
c.holdEntry(t, "lemurs", fp, lemurs) // held at the start, as the composition state keeps it, so the first pass composes
|
||||
c.holdEntry(t, "oldie", oldieFp, oldie)
|
||||
b.put(t, buckets.Wants, "want.n1.lemurs", want)
|
||||
b.put(t, buckets.Wants, "want.n1.node", nodeWant)
|
||||
for _, bucket := range []string{buckets.Wants, buckets.Assignments, buckets.Have, ResolvedBucket} {
|
||||
@@ -289,18 +300,9 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
// Issue 1966 (novox, 2026-11-09): every pass names each wanted module it composed nothing of, or composed from the indexer's
|
||||
// manifest for want of a done build, with why, and a want it did not take; said once; the agent's word that it waits
|
||||
// hands a composition the node believes handed again.
|
||||
tfp := meshcatalogclient.SourcePrefix2 + strings.Repeat("7", 64)
|
||||
tool := `{"module":"tool","version":"1","capabilities":["systemd"],"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},` +
|
||||
`"resources":[{"id":"service","type":"service","unit":"tool.service","state":"running","boot":"enabled"}]}`
|
||||
tdigest := meshcatalogclient.ManifestDigest([]byte(tool))
|
||||
tman, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "tool", Manifest: tool})
|
||||
tkey, _ := meshcatalogclient.ManifestsKey(tdigest)
|
||||
b.mu.Lock()
|
||||
b.kv[buckets.Manifests+"/"+tkey] = tman
|
||||
b.mu.Unlock()
|
||||
tmod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: tfp, Manifest: tdigest, Repository: "novox/t", Commit: "c"})
|
||||
c.takeModule(agent.Update{Key: "tool", Value: tmod, Revision: 9})
|
||||
for module, w := range map[string]meshwants.WantsByWantNodeModuleValue{"tool": {Fp: tfp, Self: true}, "ghost": {Fp: fp, Self: true}, "nofp": {Self: true}} {
|
||||
tfp, ghostFp := toolFp, meshcatalogclient.SourcePrefix2+strings.Repeat("8", 64)
|
||||
c.holdEntry(t, "tool", tfp, toolBody)
|
||||
for module, w := range map[string]meshwants.WantsByWantNodeModuleValue{"tool": {Fp: tfp, Self: true}, "ghost": {Fp: ghostFp, Self: true}, "nofp": {Self: true}} {
|
||||
raw, _ := bucketstest.Want(w)
|
||||
c.takeWant(agent.Update{Key: "want.n1." + module, Value: raw})
|
||||
}
|
||||
@@ -320,22 +322,20 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
return n
|
||||
}
|
||||
c.compose(ctx)
|
||||
why := map[string]string{"tool": c.WhyUnmade("tool"), "ghost": c.WhyUnmade("ghost"), "nofp": c.WhyUnmade("nofp"), "lemurs": c.WhyUnmade("lemurs")}
|
||||
if !strings.Contains(why["tool"], "the builder holds no done build of it at "+tfp) || !strings.Contains(why["ghost"], "places no ghost") ||
|
||||
saidOfNofp := said("the node composes nothing of nofp")
|
||||
why := map[string]string{"tool": c.LastHeld()["tool"], "ghost": c.LastHeld()["ghost"], "nofp": c.WhyUnmade("nofp"), "lemurs": c.WhyUnmade("lemurs")}
|
||||
if !strings.Contains(why["tool"], "tool at "+tfp) || !strings.Contains(why["ghost"], "entry-asked: the entry of the build "+ghostFp) ||
|
||||
!strings.Contains(why["nofp"], "want not taken: its want names no fingerprint") || why["lemurs"] != "" {
|
||||
t.Errorf("the pass says it composed nothing of %q", why)
|
||||
}
|
||||
// An update of another module moves the picture's revision: the same reasons are not said again.
|
||||
c.takeModule(agent.Update{Key: "lemurs", Value: mod, Revision: 10})
|
||||
c.compose(ctx)
|
||||
if n := said("the node composes nothing of ghost"); n != 1 {
|
||||
t.Errorf("the journal says why ghost is not composed %d times, want once", n)
|
||||
if n := said("the node composes nothing of nofp"); n != saidOfNofp || n == 0 {
|
||||
t.Errorf("the journal says why nofp is not composed %d times after the second pass, %d after the first: want the same, not none", n, saidOfNofp)
|
||||
}
|
||||
// The state of this step is taken away again, and the pass that takes back what it handed is made here.
|
||||
for _, module := range []string{"tool", "ghost", "nofp"} {
|
||||
c.takeWant(agent.Update{Key: "want.n1." + module, Deleted: true})
|
||||
}
|
||||
c.takeModule(agent.Update{Key: "tool", Deleted: true})
|
||||
c.mu.Lock()
|
||||
placed.Modules = placed.Modules[:len(placed.Modules)-1]
|
||||
c.assignments["n1"] = placed
|
||||
@@ -365,18 +365,13 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
t.Errorf("a build that cannot be composed was composed today: %s", dig)
|
||||
}
|
||||
chgJSON := strings.Replace(lemurs, `"state":"running"`, `"state":"stopped"`, 1)
|
||||
chgDigest := meshcatalogclient.ManifestDigest([]byte(chgJSON))
|
||||
chgMan, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "lemurs", Manifest: chgJSON})
|
||||
chgKey, _ := meshcatalogclient.ManifestsKey(chgDigest)
|
||||
b.mu.Lock()
|
||||
b.kv[buckets.Manifests+"/"+chgKey] = chgMan
|
||||
b.mu.Unlock()
|
||||
chgMod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: chgDigest, Repository: "novox/x", Commit: "c"})
|
||||
c.takeModule(agent.Update{Key: "lemurs", Value: chgMod})
|
||||
c.holdEntry(t, "lemurs", fp, chgJSON)
|
||||
c.layModules()
|
||||
if dig, ok := c.ComposedToday(ctx, "lemurs", fp); !ok || dig == handedDig {
|
||||
t.Errorf("composed today with a changed input: %s %v, want another digest than %s", dig, ok, handedDig)
|
||||
}
|
||||
c.takeModule(agent.Update{Key: "lemurs", Value: mod})
|
||||
c.holdEntry(t, "lemurs", fp, lemurs)
|
||||
c.layModules()
|
||||
|
||||
// Issue 1126, the switch made safe: a bus not answering for a moment (a reload of its accounts) is tried again and
|
||||
// refuses nothing; a pass whose run ended refuses nothing either.
|
||||
@@ -405,6 +400,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
for k, v := range c.modules {
|
||||
c2.modules[k] = v
|
||||
}
|
||||
maps.Copy(c2.keptEnt, c.keptEnt)
|
||||
c.mu.Unlock()
|
||||
return c2, &refusals
|
||||
}
|
||||
@@ -431,7 +427,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
b.mu.Unlock()
|
||||
failed := 0
|
||||
pctx, pcancel := context.WithCancel(ctx)
|
||||
c2, refusals := fresh(func(gctx context.Context, bucket, key string) ([]byte, error) {
|
||||
get := func(gctx context.Context, bucket, key string) ([]byte, error) {
|
||||
if failed < row.failures {
|
||||
failed++
|
||||
if row.cancel {
|
||||
@@ -447,7 +443,20 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.kv[bucket+"/"+key], nil
|
||||
})
|
||||
}
|
||||
c2, refusals := fresh(get)
|
||||
// What fails is the vault's own, asked for keeper's secret through the same read.
|
||||
c2.holdEntry(t, "keeper", keeperFp, keeperBody)
|
||||
c2.wants["keeper"] = meshwants.WantsByWantNodeModuleValue{Fp: keeperFp, Self: true}
|
||||
pk := c2.assignments["n1"]
|
||||
pk.Modules = append(slices.Clone(pk.Modules), meshmoduleassigner.PlacedModule{Module: "keeper"})
|
||||
c2.assignments["n1"] = pk
|
||||
c2.env.Own = func(octx context.Context, _ map[string]any) (json.RawMessage, error) {
|
||||
if _, err := get(octx, buckets.Wants, "own"); err != nil {
|
||||
return nil, fmt.Errorf("the vault's own could not be asked: %w", err)
|
||||
}
|
||||
return json.RawMessage(`{"node":"n1","key":"sk","own":[{"module":"keeper","name":"password","origin":"made","generation":1,"sealed":{"a":1}}],"handed":{"keeper":false}}`), nil
|
||||
}
|
||||
handed := c2.compose(pctx)
|
||||
pcancel()
|
||||
tries := 1 + len(composeAgain) // a bus paused for a moment is tried again at once; a timeout or a refusal is not
|
||||
@@ -503,15 +512,16 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
`{"id":"pkg","type":"package","package":"haproxy"}]}`
|
||||
c3, told := fresh(env.Get)
|
||||
for m, raw := range map[string]string{"de-spiegel": string(spiegel), "proxy": proxy} {
|
||||
dig := meshcatalogclient.ManifestDigest([]byte(raw))
|
||||
enc, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: m, Manifest: raw})
|
||||
k, _ := meshcatalogclient.ManifestsKey(dig)
|
||||
b.mu.Lock()
|
||||
b.kv[buckets.Manifests+"/"+k] = enc
|
||||
b.mu.Unlock()
|
||||
mfp := meshcatalogclient.SourcePrefix2 + strings.Repeat(map[string]string{"de-spiegel": "d", "proxy": "e"}[m], 64)
|
||||
c3.modules[m] = meshcatalog.ModulesByModuleValue{Fp2: mfp, Manifest: dig, Repository: "novox/" + m, Commit: "c"}
|
||||
c3.wants[m] = meshwants.WantsByWantNodeModuleValue{Fp: mfp, Self: true}
|
||||
c3.holdEntry(t, m, mfp, raw)
|
||||
w := meshwants.WantsByWantNodeModuleValue{Fp: mfp, Self: true}
|
||||
if m == "de-spiegel" { // builds an image: the entry names it, the want the digest its part has
|
||||
k := c3.keptEnt[mfp]
|
||||
k.entry.Artifacts = []meshcatalog.Artifact{{Kind: "image", Ref: "de-spiegel:site"}}
|
||||
c3.keptEnt[mfp] = k
|
||||
w.Parts = map[string]string{"de-spiegel:site": "sha256:" + strings.Repeat("6", 64)}
|
||||
}
|
||||
c3.wants[m] = w
|
||||
}
|
||||
p3 := c3.assignments["n1"]
|
||||
p3.Modules = append(append([]meshmoduleassigner.PlacedModule{}, p3.Modules...), meshmoduleassigner.PlacedModule{Module: "proxy"}, meshmoduleassigner.PlacedModule{Module: "de-spiegel"})
|
||||
@@ -556,16 +566,16 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c4, told4 := fresh(env.Get)
|
||||
powerDig := meshcatalogclient.ManifestDigest(power)
|
||||
powerEnc, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "power", Manifest: string(power)})
|
||||
powerKey, _ := meshcatalogclient.ManifestsKey(powerDig)
|
||||
b.mu.Lock()
|
||||
b.kv[buckets.Manifests+"/"+powerKey] = powerEnc
|
||||
b.mu.Unlock()
|
||||
powerFP := meshcatalogclient.SourcePrefix2 + strings.Repeat("c", 64)
|
||||
c4.modules["power"] = meshcatalog.ModulesByModuleValue{Fp2: powerFP, Manifest: powerDig, Repository: "novox/mesh-catalog", Commit: "ca8d6f33",
|
||||
// The indexer's verdict on the captured manifest (ADR 0578): the engine judges nothing, it takes this.
|
||||
Verdict: meshcatalogclient.VerdictRefused, Problems: []string{`bar item "shows" is none of them`}}
|
||||
powerHalf := catalogHalf{}
|
||||
powerHalf.add(t, "power", powerFP, string(power))
|
||||
powerEntry := powerHalf[powerFP]
|
||||
// The verdict the catalog half's entry carries on the captured manifest (ADR 0578): the engine judges nothing, it takes this.
|
||||
powerEntry.Repository, powerEntry.Commit, powerEntry.Verdict = "novox/mesh-catalog", "ca8d6f33", meshcatalogclient.VerdictRefused
|
||||
powerEntry.Inputs = &meshcatalog.EntryInputs{Problems: []string{`bar item "shows" is none of them`}}
|
||||
c4.mu.Lock()
|
||||
c4.keptEnt[powerFP] = keptEntry{entry: powerEntry}
|
||||
c4.mu.Unlock()
|
||||
c4.wants["power"] = meshwants.WantsByWantNodeModuleValue{Fp: powerFP, Self: true}
|
||||
p4 := c4.assignments["n1"]
|
||||
p4.Modules = append(append([]meshmoduleassigner.PlacedModule{}, p4.Modules...), meshmoduleassigner.PlacedModule{Module: "power"})
|
||||
@@ -735,7 +745,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
b.mu.Lock()
|
||||
n := 0
|
||||
for _, s := range b.said {
|
||||
if strings.Contains(s, "lemurs is wanted at "+gone) {
|
||||
if strings.Contains(s, "lemurs: entry-missing: the catalog half holds no entry of the build "+gone) {
|
||||
n++
|
||||
}
|
||||
}
|
||||
@@ -750,20 +760,16 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
if u := next(t, out); u.Key != lemursKey {
|
||||
t.Fatalf("lemurs wanted back handed %s", u.Key)
|
||||
}
|
||||
keeper := `{"module":"keeper","version":"1","own-secrets":{"password":"/var/lib/mesh/keeper/password"},` +
|
||||
`"resources":[{"id":"state","type":"directory","path":"/var/lib/mesh/keeper","mode":"0700"}]}`
|
||||
keeper := keeperBody
|
||||
kdigest := meshcatalogclient.ManifestDigest([]byte(keeper))
|
||||
kman, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "keeper", Manifest: keeper})
|
||||
kkey, _ := meshcatalogclient.ManifestsKey(kdigest)
|
||||
b.mu.Lock()
|
||||
b.kv[buckets.Manifests+"/"+kkey] = kman
|
||||
b.mu.Unlock()
|
||||
kfp9 := meshcatalogclient.SourcePrefix2 + strings.Repeat("9", 64)
|
||||
kmod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: kfp9, Manifest: kdigest, Repository: "novox/k", Commit: "c"})
|
||||
kfp9 := keeperFp
|
||||
both := assignedValues(t, meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"},
|
||||
Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}, {Module: "oldie"}, {Module: "keeper"}}})
|
||||
c.takeModule(agent.Update{Key: "keeper", Value: kmod})
|
||||
c.holdEntry(t, "keeper", kfp9, keeper)
|
||||
// n1 is held in keys per module and node, which the writes below move.
|
||||
bothPlaced, _ := meshmoduleassignerclient.AssignmentsOf(both)
|
||||
bothNode := bothPlaced["n1"]
|
||||
@@ -884,6 +890,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
||||
for k, v := range c.modules {
|
||||
again.modules[k] = v
|
||||
}
|
||||
maps.Copy(again.keptEnt, c.keptEnt)
|
||||
c.mu.Unlock()
|
||||
var composed string
|
||||
for _, u := range again.compose(ctx) {
|
||||
@@ -1930,7 +1937,19 @@ func TestAnUnreadSeatListHoldsWhatIsNewAndHandsItAtTheRead(t *testing.T) {
|
||||
maps.Copy(c2.assignments, c.assignments)
|
||||
maps.Copy(c2.records, c.records)
|
||||
maps.Copy(c2.modules, c.modules)
|
||||
maps.Copy(c2.keptEnt, c.keptEnt)
|
||||
c.mu.Unlock()
|
||||
// What fails is the vault's own, asked for the secret of a module the pass composes (the modules' manifests come from entries).
|
||||
kfp := meshcatalogclient.SourcePrefix2 + strings.Repeat("9", 64)
|
||||
c2.holdEntry(t, "keeper", kfp, `{"module":"keeper","version":"1","own-secrets":{"password":"/var/lib/mesh/keeper/password"},`+
|
||||
`"resources":[{"id":"state","type":"directory","path":"/var/lib/mesh/keeper","mode":"0700"}]}`)
|
||||
c2.wants["keeper"] = meshwants.WantsByWantNodeModuleValue{Fp: kfp, Self: true}
|
||||
pk := c2.assignments["n1"]
|
||||
pk.Modules = append(slices.Clone(pk.Modules), meshmoduleassigner.PlacedModule{Module: "keeper"})
|
||||
c2.assignments["n1"] = pk
|
||||
c2.env.Own = func(context.Context, map[string]any) (json.RawMessage, error) {
|
||||
return nil, errors.New("nats says ${dir:mesh-state}, and nats declares no directory")
|
||||
}
|
||||
c2.compose(ctx)
|
||||
if line := fmt.Sprintf(WaitsForTheSeatListHeld, 2, "mesh-catalog, node-engine") + "; "; !strings.HasPrefix(refusal, line) {
|
||||
t.Errorf("a pass refused while the list is unread says %q, want it to begin %q", refusal, line)
|
||||
@@ -2090,6 +2109,8 @@ func TestTheHolderOfTheCatalogIsStartedFromTheOwnCompositionBeforeTheListIsReadA
|
||||
`"resources":[{"id":"service","type":"service","unit":"other.service","state":"running","boot":"enabled"},` +
|
||||
`{"id":"extra","type":"service","unit":"extra.service","state":"running","boot":"enabled"}]}`
|
||||
b := newFakeBus()
|
||||
half := catalogHalf{}
|
||||
b.half = half
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
env := b.env()
|
||||
@@ -2107,12 +2128,11 @@ func TestTheHolderOfTheCatalogIsStartedFromTheOwnCompositionBeforeTheListIsReadA
|
||||
mods := map[string]string{"catalog": catalog, "other": other}
|
||||
var placed []meshmoduleassigner.PlacedModule
|
||||
for _, m := range []string{"catalog", "other"} {
|
||||
digest := meshcatalogclient.ManifestDigest([]byte(mods[m]))
|
||||
man, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: m, Manifest: mods[m]})
|
||||
mk, _ := meshcatalogclient.ManifestsKey(digest)
|
||||
b.kv[buckets.Manifests+"/"+mk] = man
|
||||
rec, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: meshcatalogclient.SourcePrefix2 + strings.Repeat("a", 64), Manifest: digest, Repository: "novox/x", Commit: "c"})
|
||||
c.takeModule(agent.Update{Key: m, Value: rec}) // a module run inside the node's own composition: no want names it, so no entry is asked
|
||||
mfp := meshcatalogclient.SourcePrefix2 + strings.Repeat(map[string]string{"catalog": "a", "other": "b"}[m], 64)
|
||||
half.add(t, m, mfp, mods[m])
|
||||
// A module run inside the node's own composition: no want names it, the running heard does, so its entry is asked.
|
||||
c.NoteRunning(nodeengine.Running{Node: "n1", Module: m, Fingerprint: mfp})
|
||||
c.holdEntry(t, m, mfp, mods[m])
|
||||
hk, _ := nodeengineclient.HaveKey("n1", m)
|
||||
hv, _ := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Build: "build-1", Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK})
|
||||
b.put(t, buckets.Have, hk, hv)
|
||||
|
||||
Reference in New Issue
Block a user