G §421, §683: the node-engine reads the entry its want names by fingerprint and keeps its entries and wants
The node's own composer reads the catalog half through meshcatalogclient's Entries cache over its own calls of mesh-catalog's verb entry, and no longer watches or reads the indexer's modules, manifests and verdicts or the builder's builds: a module's manifest is its entry's, each artifact's reference is made from the digest its want's part names (artifact-store://<module>/<artifact>/blobs/<digest>, A-50). A want whose entry is asked or not found is held, its have the phase held with a detail opening entry-asked: or entry-missing: naming the fingerprint. The entries and wants it composes from are kept in composed-<node> (entry.<digits> and want.<module>), written before the composition that uses them, read at start before anything is asked, a kept want standing until the wants' live read answers, and dropped when nothing names them. The shadow keeps its reads (G §628). The node grant gains the verb entry and the running stream's consumer calls; OverNATS hears the running stream as the link's session does. Builds: design 47 G §421, §683, §614, §628, A-47, A-50
This commit is contained in:
@@ -1135,6 +1135,9 @@ func selfComposer(session *link.Session, node string, hooks composerHooks, say f
|
||||
env.ComposedState = link.EngineComposed{Session: session, Node: node}
|
||||
// And each composition it writes there said as the seat's event composed, which the assigner waits on.
|
||||
env.SayComposed = session.SayComposed
|
||||
// And the catalog half read by the fingerprints its wants and runnings name, over its own calls of the seat mesh-catalog's
|
||||
// verb entry on its link session (design 47 G §421): neither the indexer's nor the builder's states are read.
|
||||
env.Entry = selfcompose.EntryCall(session.AskMeshSeat)
|
||||
return selfcompose.New(env)
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,55 @@ type itBed struct {
|
||||
// holder re-serves (issue 1966).
|
||||
seatAway atomic.Bool
|
||||
findings nats.KeyValue
|
||||
// entries are the entries the stand-in of the seat mesh-catalog answers its verb entry from, by fingerprint (design 47 G §421).
|
||||
entries map[string]meshcatalog.CatalogEntry
|
||||
}
|
||||
|
||||
// registerEntry makes the stand-in answer the entry of fp: module's manifest and its artifacts (refs `<module>:<name>`).
|
||||
func (b *itBed) registerEntry(module, fp, manifest string, artifacts ...string) {
|
||||
b.t.Helper()
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal([]byte(manifest), &m); err != nil {
|
||||
b.t.Fatal(err)
|
||||
}
|
||||
e := meshcatalog.CatalogEntry{Module: module, Fp: fp, Manifest: m, ManifestDigest: meshcatalogclient.ManifestDigest([]byte(manifest)),
|
||||
Repository: "novox/" + module, Commit: strings.Repeat("1", 40)}
|
||||
for _, a := range artifacts {
|
||||
e.Artifacts = append(e.Artifacts, meshcatalog.Artifact{Ref: module + ":" + a, Kind: "archive", Language: "go"})
|
||||
}
|
||||
b.mu.Lock()
|
||||
b.entries[fp] = e
|
||||
b.mu.Unlock()
|
||||
}
|
||||
|
||||
// running says the seat node-engine's event running of module on node at fp, as that node's engine does.
|
||||
func (b *itBed) running(node, module, fp, manifestDigest string) {
|
||||
b.t.Helper()
|
||||
raw, _ := json.Marshal(nodeengine.Running{V: 1, Node: node, Module: module, Fingerprint: fp, Manifest: manifestDigest, At: time.Now().UTC()})
|
||||
if _, err := b.js.Publish(nodeengine.SubjectRunning(node, module), raw); err != nil {
|
||||
b.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// standInEntry answers the seat mesh-catalog's verb entry from the registered entries, and as the holder refuses an unknown one.
|
||||
func (b *itBed) standInEntry() {
|
||||
sub, err := b.conn.Subscribe("mesh.seat.mesh-catalog.call.entry.*", func(m *nats.Msg) {
|
||||
var in meshcatalog.EntryInput
|
||||
_ = json.Unmarshal(m.Data, &in)
|
||||
b.mu.Lock()
|
||||
e, ok := b.entries[in.Fingerprint]
|
||||
b.mu.Unlock()
|
||||
if !ok {
|
||||
_ = m.Respond([]byte(`{"error":"no entry of fingerprint ` + in.Fingerprint + ` is kept"}`))
|
||||
return
|
||||
}
|
||||
out, _ := json.Marshal(map[string]any{"result": meshcatalog.EntryAnswer{Entry: e}})
|
||||
_ = m.Respond(out)
|
||||
})
|
||||
if err != nil {
|
||||
b.t.Fatal(err)
|
||||
}
|
||||
b.t.Cleanup(func() { _ = sub.Unsubscribe() })
|
||||
}
|
||||
|
||||
// aServer is an embedded bus with JetStream and two users: the test's, unrestricted, and the node's, granted exactly what
|
||||
@@ -131,7 +180,7 @@ func newItBedAs(t *testing.T, perms nodegrant.Permissions) *itBed {
|
||||
builtFor = "arch"
|
||||
t.Cleanup(func() { builtFor = was })
|
||||
|
||||
b := &itBed{t: t, dir: t.TempDir()}
|
||||
b := &itBed{t: t, dir: t.TempDir(), entries: map[string]meshcatalog.CatalogEntry{}}
|
||||
b.conn, b.nodeConn = aServer(t, perms, func(err error) {
|
||||
b.mu.Lock()
|
||||
b.refusals = append(b.refusals, err.Error())
|
||||
@@ -167,7 +216,13 @@ func newItBedAs(t *testing.T, perms nodegrant.Permissions) *itBed {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// The stream the seat node-engine's event running is kept in, the last of each subject (design 47 G §199).
|
||||
if _, err = b.js.AddStream(&nats.StreamConfig{Name: nodeengine.StreamRunning, Subjects: []string{nodeengine.SubjectRunningAll},
|
||||
MaxMsgsPerSubject: 1}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.standInSeat()
|
||||
b.standInEntry()
|
||||
b.opts = options{state: filepath.Join(b.dir, "state", "state.json"), timeout: 2 * time.Second}
|
||||
if err := os.MkdirAll(filepath.Dir(b.opts.state), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -435,8 +490,7 @@ func h8(t *testing.T, b *itBed, checkBuild bool) (func(), h8state) {
|
||||
path := filepath.Join(b.dir, "web.conf")
|
||||
man := `{"module":"web","version":"1","config":{"greeting":{"type":"string","description":"what the file says","default":"hello"}},` +
|
||||
`"resources":[{"id":"conf","type":"file","path":"` + path + `","mode":"0644","content":"greeting=${config:greeting}\n"},` +
|
||||
`{"id":"tools","type":"package","package":"web-tools"}],` +
|
||||
`"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]}}`
|
||||
`{"id":"tools","type":"package","package":"web-tools"}]}`
|
||||
digest := meshcatalogclient.ManifestDigest([]byte(man))
|
||||
mraw, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "web", Manifest: man})
|
||||
mkey, _ := meshcatalogclient.ManifestsKey(digest)
|
||||
@@ -464,9 +518,6 @@ func h8(t *testing.T, b *itBed, checkBuild bool) (func(), h8state) {
|
||||
done, _ := builderbuilds.EncodeBuilderBuild(builderbuilds.BuilderBuild{State: builderbuilds.BuildDone, Manifest: digest,
|
||||
Artifacts: []builderbuilds.BuiltArtifact{{Name: "files", Kind: "archive", Reference: "artifact-store://web/files/blobs/" + fpDigest("f"), Digest: fpDigest("f")}}})
|
||||
firstBuild := done
|
||||
if checkBuild {
|
||||
firstBuild = []byte(`{"v":1,"state":"no such state"}`)
|
||||
}
|
||||
for _, w := range []struct {
|
||||
kv nats.KeyValue
|
||||
key string
|
||||
@@ -479,6 +530,13 @@ func h8(t *testing.T, b *itBed, checkBuild bool) (func(), h8state) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// Design 47 G §421: the node reads the entry its want names, through the seat mesh-catalog's verb entry, and the part's digest
|
||||
// from the want; what it hears another node run (the mailer on it2) is read by the same fingerprint.
|
||||
if !checkBuild {
|
||||
b.registerEntry("web", fp, man)
|
||||
}
|
||||
b.registerEntry("mailer", fpIt("b"), mailer)
|
||||
b.running("it2", "mailer", fpIt("b"), mdigest)
|
||||
want, _ := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Fp: fp, Self: true})
|
||||
wkey := meshwants.WantsByWantNodeModuleKey(itNode, "web")
|
||||
if _, err := b.wants.Put(wkey, want); err != nil {
|
||||
@@ -499,17 +557,19 @@ func h8(t *testing.T, b *itBed, checkBuild bool) (func(), h8state) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if checkBuild {
|
||||
// The build of the placed module is read through the node's session: one that does not decode is said, and nothing
|
||||
// is applied; a done one, then a config event, and the node composes.
|
||||
within(t, 5*time.Second, "the node did not read web's build key through its session", func() bool {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return strings.Contains(strings.Join(b.said, "\n"), "the builder's "+buildKey)
|
||||
_ = buildKey
|
||||
// The entry of the build its want names is asked of the catalog half through the node's session: one the half holds none
|
||||
// of holds the want (entry-missing, naming the fingerprint) and nothing is applied; once the half holds it and it is
|
||||
// asked again by hand, then a config event, and the node composes.
|
||||
within(t, 5*time.Second, "the have does not say the entry of web's build is missing", func() bool {
|
||||
h := b.have("web")
|
||||
return h.Phase == nec.PhaseHeld && strings.HasPrefix(h.Detail, agent.EntryMissing) && strings.Contains(h.Detail, fp)
|
||||
})
|
||||
if b.file("web") != "" {
|
||||
t.Fatal("composed with a build key that does not decode")
|
||||
t.Fatal("composed with an entry the catalog half does not hold")
|
||||
}
|
||||
if _, err := builds.Put(buildKey, done); err != nil {
|
||||
b.registerEntry("web", fp, man)
|
||||
if err := composerNow.Load().EntryAgain(context.Background(), fp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := resolved.Put(itNode+".web.greeting", []byte(`{"value":"bonjour","source":"module"}`)); err != nil {
|
||||
@@ -799,6 +859,7 @@ func TestNatsTheBusServerIsTakenOverAndReplacedOnlyWhenItsBuildChangesIt(t *test
|
||||
mraw, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "nats", Manifest: man})
|
||||
mkey, _ := meshcatalogclient.ManifestsKey(digest)
|
||||
modv, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: digest, Repository: "novox/mesh-catalog", Commit: strings.Repeat("3", 40)})
|
||||
b.registerEntry("nats", fp, man) // the node reads the entry its want names (design 47 G §421)
|
||||
if _, err := manifests.Put(mkey, mraw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user