AS14 (a): an assignment entry held for want of a provider names the candidates
A requirement neither bound nor pinned points to the provider on the consumer's node, else the only provider; otherwise the entry carries the new field held (seat mesh-wants' AssignmentEntry, generated by seatgen), naming each placed provider as a candidate, and nothing is guessed. The named seat's holder and the contribution edge wait for their design line. Builds: design 47 G §357; lead answer A-167 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -58,13 +58,16 @@ type placedAt struct {
|
||||
entry meshwants.AssignmentEntry
|
||||
langs map[string]bool
|
||||
acts []actingRule
|
||||
// provides is what its catalog entry provides; open its requirements its placement neither binds nor its node pins.
|
||||
provides, open []string
|
||||
}
|
||||
|
||||
type actingRule struct{ seat, step, on, bound string }
|
||||
|
||||
// Compute is the assignment graph revision of in: one entry per module placed on a node whose branch's catalog graph
|
||||
// holds it, its version the build the placement names or else the catalog entry's fingerprint (design 47 lines 909-914),
|
||||
// with the edges of provisioning to the providers its placement binds or its node pins, and of acting from each acting
|
||||
// with the edges of provisioning to the providers its placement binds or its node pins, else the provider on its node or
|
||||
// the only provider (an entry held naming the candidates where none is), and of acting from each acting
|
||||
// part's version to each version its rule names (design 47 *An acting part's new version acts first*: "the assigner
|
||||
// gives an edge from the acting part's version to each version its rule names").
|
||||
func Compute(in Inputs) Computed {
|
||||
@@ -115,6 +118,7 @@ func Compute(in Inputs) Computed {
|
||||
for _, r := range ce.Acts {
|
||||
p.acts = append(p.acts, actingRule{seat: r.Seat, step: r.Step, on: r.On, bound: r.Bound})
|
||||
}
|
||||
p.provides = ce.Provides
|
||||
for _, r := range ce.Requires {
|
||||
prov, ok := m.Bindings[r]
|
||||
if !ok {
|
||||
@@ -122,12 +126,42 @@ func Compute(in Inputs) Computed {
|
||||
}
|
||||
if ok {
|
||||
p.entry.Edges = append(p.entry.Edges, meshwants.Edge{Kind: EdgeProvisioning, Node: prov.Node, Module: prov.Module, Provision: r})
|
||||
} else {
|
||||
p.open = append(p.open, r)
|
||||
}
|
||||
}
|
||||
placed = append(placed, p)
|
||||
branches[branch] = true
|
||||
}
|
||||
}
|
||||
// A requirement neither bound nor pinned points, by design 27's order (design 47 G §357), to the provider on the
|
||||
// consumer's node, else to the only provider; otherwise the entry is held, naming the candidates, and nothing is guessed.
|
||||
for _, p := range placed {
|
||||
for _, r := range p.open {
|
||||
var here, all []*placedAt
|
||||
for _, q := range placed {
|
||||
if q != p && slices.Contains(q.provides, r) {
|
||||
all = append(all, q)
|
||||
if q.node == p.node {
|
||||
here = append(here, q)
|
||||
}
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case len(here) == 1:
|
||||
p.entry.Edges = append(p.entry.Edges, meshwants.Edge{Kind: EdgeProvisioning, Node: here[0].node, Module: here[0].placed.Module, Provision: r})
|
||||
case len(here) == 0 && len(all) == 1:
|
||||
p.entry.Edges = append(p.entry.Edges, meshwants.Edge{Kind: EdgeProvisioning, Node: all[0].node, Module: all[0].placed.Module, Provision: r})
|
||||
default:
|
||||
h := meshwants.Held{Provision: r}
|
||||
for _, q := range all {
|
||||
h.Candidates = append(h.Candidates, meshwants.Edge{Kind: EdgeProvisioning, Node: q.node, Module: q.placed.Module, Fp: q.entry.Fp, Provision: r})
|
||||
}
|
||||
sortEdges(h.Candidates)
|
||||
p.entry.Held = append(p.entry.Held, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
at := map[string]*placedAt{}
|
||||
for _, p := range placed {
|
||||
at[p.node+"/"+p.placed.Module] = p
|
||||
|
||||
@@ -91,11 +91,16 @@ func TestTheAssignmentGraphIsComputedFromItsInputsAndKept(t *testing.T) {
|
||||
if fmt.Sprint(webA.Edges) != fmt.Sprint(want) {
|
||||
t.Errorf("web on anchor's edges:\n got %+v\nwant %+v", webA.Edges, want)
|
||||
}
|
||||
// laptop's web: its own node's node-engine composes it, the build module placed once builds it, and no binding or pin
|
||||
// names a provider there.
|
||||
if e := webL.Edges; len(e) != 2 || e[0].Module != "build-go" || e[0].Node != "anchor" || e[1].Module != "node-engine" || e[1].Node != "laptop" {
|
||||
// laptop's web: its own node's node-engine composes it, the build module placed once builds it, and with no binding or
|
||||
// pin its database is the only provider's (design 47 G §357).
|
||||
if e := webL.Edges; len(e) != 3 || e[0].Module != "build-go" || e[0].Node != "anchor" || e[1].Module != "node-engine" || e[1].Node != "laptop" ||
|
||||
e[2] != (meshwants.Edge{Kind: EdgeProvisioning, Node: "anchor", Module: "postgres", Fp: "src2:p1", Provision: "database"}) {
|
||||
t.Errorf("web on laptop's edges: %+v", e)
|
||||
}
|
||||
// Its secrets no placed module provides: held on both nodes, naming no candidate.
|
||||
if h := webA.Held; len(h) != 1 || h[0].Provision != "secrets" || len(h[0].Candidates) != 0 {
|
||||
t.Errorf("web on anchor is held on %+v", h)
|
||||
}
|
||||
if e := byNode["anchor/postgres"].Edges; len(e) != 1 || e[0].Module != "node-engine" {
|
||||
t.Errorf("postgres builds no Go, so only the node-engine acts on it: %+v", e)
|
||||
}
|
||||
@@ -149,6 +154,63 @@ func TestTheAssignmentGraphIsComputedFromItsInputsAndKept(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Design 47 G §357 and A-167: a requirement neither bound nor pinned points to the provider on the consumer's node, else
|
||||
// to the only provider; otherwise the entry is held, naming the candidates, and nothing is guessed.
|
||||
func TestARequirementPointsByTheOrderOrIsHeld(t *testing.T) {
|
||||
h := half("main", 3,
|
||||
meshcatalog.CatalogEntry{Module: "kv", Fp: "src2:k1", Provides: []string{"cache"}},
|
||||
meshcatalog.CatalogEntry{Module: "web", Fp: "src2:w1", Requires: []string{"cache"}})
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
kv []string // the nodes kv is placed on
|
||||
pin bool // web's node pins kv on laptop
|
||||
edge string // the node of the provisioning edge, "" for none
|
||||
held string // the candidates' nodes of the hold, "-" for no hold
|
||||
}{
|
||||
{name: "the provider on the consumer's node", kv: []string{"anchor", "laptop"}, edge: "anchor", held: "-"},
|
||||
{name: "the only provider", kv: []string{"laptop"}, edge: "laptop", held: "-"},
|
||||
{name: "a pin before the node's own", kv: []string{"anchor", "laptop"}, pin: true, edge: "laptop", held: "-"},
|
||||
{name: "two providers elsewhere: held", kv: []string{"laptop", "server"}, held: "laptop,server"},
|
||||
{name: "none: held naming none", held: ""},
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
pl := map[string]mma.Assignments{"anchor": {Node: "anchor", Modules: []mma.PlacedModule{{Module: "web"}}}}
|
||||
if c.pin {
|
||||
pl["anchor"] = mma.Assignments{Node: "anchor", Modules: pl["anchor"].Modules, Pins: map[string]mma.Provider{"cache": {Node: "laptop", Module: "kv"}}}
|
||||
}
|
||||
for _, n := range c.kv {
|
||||
a := pl[n]
|
||||
a.Node, a.Modules = n, append(a.Modules, mma.PlacedModule{Module: "kv"})
|
||||
pl[n] = a
|
||||
}
|
||||
out := Compute(Inputs{Halves: map[string]meshcatalogclient.Half{"main": h}, Placements: pl, FirstBranch: func(string) string { return "main" }})
|
||||
var web meshwants.AssignmentEntry
|
||||
for _, ref := range out.Revision.Entries {
|
||||
if ref.Node == "anchor" && ref.Module == "web" {
|
||||
web = out.Entries[ref.Digest]
|
||||
}
|
||||
}
|
||||
edge := ""
|
||||
for _, e := range web.Edges {
|
||||
if e.Kind == EdgeProvisioning && e.Provision == "cache" {
|
||||
edge = e.Node
|
||||
}
|
||||
}
|
||||
held := "-"
|
||||
if len(web.Held) == 1 && web.Held[0].Provision == "cache" {
|
||||
var nodes []string
|
||||
for _, cand := range web.Held[0].Candidates {
|
||||
nodes = append(nodes, cand.Node)
|
||||
}
|
||||
held = strings.Join(nodes, ",")
|
||||
}
|
||||
if edge != c.edge || held != c.held || len(web.Held) > 1 {
|
||||
t.Errorf("web points to %q, held on %q (%+v); want %q, %q", edge, held, web.Held, c.edge, c.held)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Design 47 line 1385: a whole assignment graph revision of about 120 modules on four nodes is under the bus's 256 KiB
|
||||
// value limit.
|
||||
func TestARevisionOf120ModulesOnFourNodesFitsOneValue(t *testing.T) {
|
||||
|
||||
@@ -472,7 +472,9 @@ type AssignmentEntry struct {
|
||||
Catalog string `json:"catalog"`
|
||||
Edges []Edge `json:"edges,omitempty"`
|
||||
// the fingerprint it is to run
|
||||
Fp string `json:"fp"`
|
||||
Fp string `json:"fp"`
|
||||
// the requirements no step of the order resolves (design 47 G section 357): the entry is held, naming the candidates, and nothing is guessed
|
||||
Held []Held `json:"held,omitempty"`
|
||||
Module string `json:"module"`
|
||||
Node string `json:"node"`
|
||||
// the layout version
|
||||
@@ -582,6 +584,13 @@ type Fill struct {
|
||||
Store string `json:"store"`
|
||||
}
|
||||
|
||||
// Held is one requirement an entry is held on, for want of a provider.
|
||||
type Held struct {
|
||||
// a provisioning edge to each placed provider of it, none chosen; none when no placed module provides it
|
||||
Candidates []Edge `json:"candidates,omitempty"`
|
||||
Provision string `json:"provision"`
|
||||
}
|
||||
|
||||
// NodeEntryRef is a type of the contract.
|
||||
type NodeEntryRef struct {
|
||||
// the entry's digest, its object's name
|
||||
|
||||
@@ -825,6 +825,13 @@
|
||||
"items": {
|
||||
"$ref": "#/defs/Edge"
|
||||
}
|
||||
},
|
||||
"held": {
|
||||
"type": "array",
|
||||
"description": "the requirements no step of the order resolves (design 47 G section 357): the entry is held, naming the candidates, and nothing is guessed",
|
||||
"items": {
|
||||
"$ref": "#/defs/Held"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -835,6 +842,25 @@
|
||||
"catalog"
|
||||
]
|
||||
},
|
||||
"Held": {
|
||||
"type": "object",
|
||||
"description": "one requirement an entry is held on, for want of a provider",
|
||||
"properties": {
|
||||
"provision": {
|
||||
"type": "string"
|
||||
},
|
||||
"candidates": {
|
||||
"type": "array",
|
||||
"description": "a provisioning edge to each placed provider of it, none chosen; none when no placed module provides it",
|
||||
"items": {
|
||||
"$ref": "#/defs/Edge"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"provision"
|
||||
]
|
||||
},
|
||||
"NodeEntryRef": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -143,6 +143,8 @@ export interface AssignmentEntry {
|
||||
"edges"?: Edge[];
|
||||
/** the fingerprint it is to run */
|
||||
"fp": string;
|
||||
/** the requirements no step of the order resolves (design 47 G section 357): the entry is held, naming the candidates, and nothing is guessed */
|
||||
"held"?: Held[];
|
||||
"module": string;
|
||||
"node": string;
|
||||
/** the layout version */
|
||||
@@ -250,6 +252,13 @@ export interface Fill {
|
||||
"store": string;
|
||||
}
|
||||
|
||||
/** one requirement an entry is held on, for want of a provider */
|
||||
export interface Held {
|
||||
/** a provisioning edge to each placed provider of it, none chosen; none when no placed module provides it */
|
||||
"candidates"?: Edge[];
|
||||
"provision": string;
|
||||
}
|
||||
|
||||
export interface NodeEntryRef {
|
||||
/** the entry's digest, its object's name */
|
||||
"digest": string;
|
||||
|
||||
Reference in New Issue
Block a user