The grants part composes what the catalogue's placement places beside the assignments, and holds a seat held once for the mesh where placement says (issue 1126, ADR 0549 §3, G5)
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: unit: git.novox.be/novox/mesh-catalog/checks/nobinaries.TestTheCatalogueHoldsNoCompiledProgram: modules/mesh-config/cmd/mesh-config…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: unit: git.novox.be/novox/mesh-catalog/checks/nobinaries.TestTheCatalogueHoldsNoCompiledProgram: modules/mesh-config/cmd/mesh-config…
This commit is contained in:
@@ -3,7 +3,7 @@ module mesh-grants
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026142434-65ee43f07583
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026182519-53c2def1a520
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026142434-65ee43f07583 h1:ikD/ggf9/5k23sxJOX1PFcGzb4M3+QeC66AT0MyQ9Zs=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026142434-65ee43f07583/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026182519-53c2def1a520 h1:twiziR88X6ttKmw5JTd3aW6xb5bMZZ3Bl7WS2QOP3PE=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026182519-53c2def1a520/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
@@ -708,6 +708,43 @@ func TestGrants(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// 1126 (ADR 0549 §1, §3): once the catalogue places the nodes, a seat held once for the mesh is held where placement
|
||||
// says, not on every machine running a module that claims it; what placement adds is composed beside the assignments.
|
||||
t.Run("a mesh seat is held where placement says", func(t *testing.T) {
|
||||
store := catalogue.Manifest{Module: "store", Version: "1",
|
||||
DefinesSeats: []catalogue.SeatDeclaration{{Name: "store-seat", Scope: catalogue.ScopeMesh, Serves: []catalogue.Verb{{Name: "databases"}}}},
|
||||
Claims: []catalogue.Claim{{Name: "store-seat", Scope: catalogue.ScopeMesh}}}
|
||||
in := grants.Inputs{Nodes: []string{"a", "b"}, Assigned: map[string][]string{"a": {"store"}, "b": {"store"}},
|
||||
Manifests: map[string]catalogue.Manifest{"store": store}}
|
||||
holds := func(recs buscompose.Records, node string) int {
|
||||
n := 0
|
||||
for _, d := range recs.Assigned[node] {
|
||||
n += len(d.Holds)
|
||||
}
|
||||
return n
|
||||
}
|
||||
if recs, _ := grants.Records(in); holds(recs, "a") != 1 || holds(recs, "b") != 1 {
|
||||
t.Fatalf("before placement the seat is held on a %d and b %d times, want both", holds(recs, "a"), holds(recs, "b"))
|
||||
}
|
||||
in.Placed = map[string]delivery.Placement{
|
||||
"a": {Node: "a", Modules: []delivery.PlacedModule{{Module: "store"}}, Holds: []delivery.Holding{{Claim: "store-seat", Scope: "mesh", Module: "store"}}},
|
||||
"b": {Node: "b", Modules: []delivery.PlacedModule{{Module: "store"}}}}
|
||||
if recs, _ := grants.Records(in); holds(recs, "a") != 1 || holds(recs, "b") != 0 {
|
||||
t.Errorf("placed, the seat is held on a %d and b %d times, want a alone", holds(recs, "a"), holds(recs, "b"))
|
||||
}
|
||||
b := fixture()
|
||||
pl, _ := delivery.EncodePlacement(delivery.Placement{Node: "b", Modules: []delivery.PlacedModule{{Module: "mod-y"}, {Module: "mod-x"}}})
|
||||
b.states[grants.ReadPlacement] = map[string][]byte{"placement.b": pl}
|
||||
read := grants.ReadInputs(b, &grants.Manifests{})
|
||||
if !slices.Contains(read.Assigned["b"], "mod-x") || read.Placed["b"].Node != "b" || slices.ContainsFunc(read.Gaps, func(g grants.Gap) bool { return g.Input == "seat holdings" }) {
|
||||
t.Errorf("read: assigned %v, placed %v, gaps %v", read.Assigned, read.Placed, read.Gaps)
|
||||
}
|
||||
b.refused[grants.ReadPlacement] = true
|
||||
if read := grants.ReadInputs(b, &grants.Manifests{}); slices.ContainsFunc(read.Gaps, func(g grants.Gap) bool { return g.Input == grants.ReadPlacement && g.Failed }) {
|
||||
t.Errorf("placement not on the bus is a failed read: %v", read.Gaps)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("compare excuses a retired verb and a multiply claimed mesh seat's added subscriptions on its machines, nothing else", func(t *testing.T) {
|
||||
const retired, store, storeInfo = "mesh.seat.mesh-delivery.tool.mark-ready", "mesh.seat.mesh-store.tool.databases", "$SRV.INFO.mesh-store.>"
|
||||
two := map[string][]string{"mesh-store": {"a", "n"}}
|
||||
|
||||
@@ -18,7 +18,7 @@ type Watcher interface {
|
||||
// Followed are the states whose change composes again: placement, the indexer's manifests by module, the resolved
|
||||
// config values, the node agents' haves (a machine's first have, and the bus node's loaded list) and the assigner's
|
||||
// wants (the manifest each machine is wanted to run), and the vault's hashes of the bus accounts.
|
||||
var Followed = []string{ReadAssignments, ReadModules, ReadResolved, ReadHave, ReadWants, ReadAccounts}
|
||||
var Followed = []string{ReadAssignments, ReadModules, ReadResolved, ReadHave, ReadWants, ReadAccounts, ReadPlacement}
|
||||
|
||||
// Follower composes again on every change it hears, never on a timer. It never stops for a refusal: a watch it cannot
|
||||
// open is said, composed without, and opened again at the next wake (any change it does hear, or a reconnect).
|
||||
|
||||
@@ -34,11 +34,14 @@ const (
|
||||
ReadBuilds = "mesh-delivery-builder.builds"
|
||||
// ReadAccounts is the vault's bcrypt hashes of the bus accounts it holds, by bus user (ADR 0547 clause 9).
|
||||
ReadAccounts = "mesh-vault.bus-accounts"
|
||||
// ReadPlacement is the indexer's placement, read from the catalogue (ADR 0549 §2, §3): each node's modules and the seats
|
||||
// they hold.
|
||||
ReadPlacement = "mesh-delivery-indexer.placement"
|
||||
)
|
||||
|
||||
// Reads is every state the part reads, in the manifest's order.
|
||||
var Reads = []string{ReadAssignments, ReadControllerGrant, ReadModules, ReadManifests, ReadResolved, ReadHave, ReadWants, ReadBuilds,
|
||||
ReadAccounts}
|
||||
ReadAccounts, ReadPlacement}
|
||||
|
||||
// The bus module's tools the part calls, as its manifest's invokes name them: read-only, each.
|
||||
const (
|
||||
@@ -103,8 +106,12 @@ type resolved struct {
|
||||
type Inputs struct {
|
||||
// Nodes is every machine seen: in a node agent's have or a standing assignment.
|
||||
Nodes []string
|
||||
// Assigned is each machine's standing assignments, sorted.
|
||||
// Assigned is each machine's standing assignments and what the catalogue's placement puts there, sorted: while both
|
||||
// stand (ADR 0549 §5) a module either names keeps its membership.
|
||||
Assigned map[string][]string
|
||||
// Placed is each node's placement as the indexer read it from the catalogue (ADR 0549 §2); empty before any is read.
|
||||
// Where it is, a seat held once for the mesh is held where placement says, not wherever a module claiming it runs.
|
||||
Placed map[string]delivery.Placement
|
||||
// Manifests is each module's manifest at the indexer's trunk, by module.
|
||||
Manifests map[string]catalogue.Manifest
|
||||
// Digests is each module's manifest digest, by module.
|
||||
@@ -230,6 +237,38 @@ func ReadInputs(r Reader, cache *Manifests) Inputs {
|
||||
in.BusNode = a.Node
|
||||
}
|
||||
}
|
||||
// The catalogue's placement (ADR 0549 §3): what it places is composed beside the assignments, and the seats held once
|
||||
// for the mesh are held where it says. Not on the bus yet is a standing gap, never a failed read, so the list is still
|
||||
// composed and sealed from the assignments alone.
|
||||
if keys, err := r.Keys(ReadPlacement); err != nil {
|
||||
in.Gaps = append(in.Gaps, Gap{Input: ReadPlacement, Why: "not read yet: the assignments alone place modules (" + err.Error() + ")"})
|
||||
} else {
|
||||
for _, k := range keys {
|
||||
raw, found, err := r.Get(ReadPlacement, k)
|
||||
if err != nil || !found {
|
||||
continue
|
||||
}
|
||||
p, err := delivery.DecodePlacement(raw)
|
||||
node, kerr := delivery.ParsePlacementKey(k)
|
||||
if err != nil || kerr != nil || p.Node != node {
|
||||
in.Gaps = append(in.Gaps, Gap{Input: ReadPlacement + " " + k, Why: "not a placement, so the node is composed from its assignments alone"})
|
||||
continue
|
||||
}
|
||||
if in.Placed == nil {
|
||||
in.Placed = map[string]delivery.Placement{}
|
||||
}
|
||||
in.Placed[node] = p
|
||||
nodes[node] = true
|
||||
for _, m := range p.Assigned() {
|
||||
if !slices.Contains(in.Assigned[node], m) {
|
||||
in.Assigned[node] = append(in.Assigned[node], m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(in.Placed) > 0 {
|
||||
in.Gaps = slices.DeleteFunc(in.Gaps, func(g Gap) bool { return g.Input == "seat holdings" })
|
||||
}
|
||||
haveKeys, err := r.Keys(ReadHave)
|
||||
if err != nil {
|
||||
in.Gaps = append(in.Gaps, Gap{Input: ReadHave, Why: "refused or absent: " + err.Error(), Failed: true})
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// Records is what the bus composer composes from, rebuilt from the bus (mesh-controller internal/inventory busrecords.go,
|
||||
@@ -59,6 +60,9 @@ func Records(in Inputs) (buscompose.Records, []Gap) {
|
||||
continue
|
||||
}
|
||||
d := declaredFor(m, seats, declarers)
|
||||
if len(in.Placed) > 0 {
|
||||
d.Holds = heldWherePlaced(d.Holds, in.Placed[node], m.Module)
|
||||
}
|
||||
for _, sr := range catalogue.ReadsGranted(m, here, node) {
|
||||
d.KeyedReads = append(d.KeyedReads, buscompose.KeyedRead{Read: sr.Read, Key: sr.Key})
|
||||
}
|
||||
@@ -299,3 +303,22 @@ func hearsOf(m catalogue.Manifest, declared map[string]catalogue.Manifest, node
|
||||
}
|
||||
return out, touches, onEvents
|
||||
}
|
||||
|
||||
// heldWherePlaced is a module's holds on a node as the catalogue's placement says them (ADR 0549 §1, §3): a seat held once
|
||||
// for the mesh is held there only when the node's placement names this module holding it, so a module claiming such a seat
|
||||
// on two machines holds it on the one placement names, not on both; a seat of every node is held wherever the module runs.
|
||||
func heldWherePlaced(holds []buscompose.Seat, p delivery.Placement, module string) []buscompose.Seat {
|
||||
var out []buscompose.Seat
|
||||
for _, h := range holds {
|
||||
if h.Scope != "" && h.Scope != catalogue.ScopeMesh {
|
||||
out = append(out, h)
|
||||
continue
|
||||
}
|
||||
if slices.ContainsFunc(p.Holds, func(x delivery.Holding) bool {
|
||||
return x.Claim == h.Name && x.Module == module && x.Kind == h.Kind
|
||||
}) {
|
||||
out = append(out, h)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"mesh-delivery-builder.builds",
|
||||
"mesh-delivery-indexer.manifests",
|
||||
"mesh-delivery-indexer.modules",
|
||||
"mesh-delivery-indexer.placement",
|
||||
"node-engine.have",
|
||||
"mesh-vault.bus-accounts"
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user