A node's plans took 2.6-13.6 s of CPU on the provisioner: one composition resolved every machine of the mesh some two hundred times (who is on the private network, and the rest of the mesh, worked out again on every look), three compositions did so again each, and the holder composed twice. A composer now works each out once over its unchanging Input; Composing gives Resolve, Render and Generators one composer; PlansHeldTwice composes the wanted and the newest builds over one read of the bus, says where the time went by phase, and the states reader keeps a build's take-in by its content. WatchedStates takes a process's own writes into the copy at once, so a holder reads its own states from it. Measured on a captured novox mesh: 11.7 s to 0.49 s per composition of an unchanged node, plans and provisions identical on all four nodes. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
151 lines
5.7 KiB
Go
151 lines
5.7 KiB
Go
package compose
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// Rendered is everything a node's declaration is composed with (plan.go renderingFor), over an Input: the place half (Places)
|
|
// and the credential half. Nothing was allocated, made, kept or written; what a send would do is returned for the caller to do.
|
|
type Rendered struct {
|
|
Rendering catalogue.Rendering
|
|
// ForeseenPorts are the ports the mesh may assign that the Input holds none for ("<module>:<port>").
|
|
ForeseenPorts []string
|
|
// Foreseen are the stand-ins the composition used for what the Input does not hold: an own secret the next send makes
|
|
// ("<module>/<name>"), a grant's sealed copy, the certificate and the bus's user list.
|
|
Foreseen []string
|
|
// CoreBoxesWanted are the core credentials whose box the send must ask the vault for and keep.
|
|
CoreBoxesWanted []CoreBoxWanted
|
|
// Withheld are the consumers the grants leave out because their identity overflows the provision's bound (novox/hq ADR 0225),
|
|
// and Unbound those whose credential from this node is on record and whose own resolution binds them elsewhere (issue
|
|
// 274). The controller says them; a check refuses a change that adds one (ADR 0446 clause 1).
|
|
Withheld []catalogue.Overflow
|
|
Unbound []catalogue.Unbound
|
|
// Said are the lines the controller says while composing (the bus users left out for want of a credential).
|
|
Said []string
|
|
}
|
|
|
|
// Render composes the rendering of a node: the grants it must create, where it puts its ports, its own secrets, the vault's boxes,
|
|
// its certificate, the names of the mesh, the bus's user list and its membership. The refusals are the controller's, in its words;
|
|
// where more than one fault stands, the first reported may differ from the controller's, which interleaves the halves.
|
|
func Render(in Input, node string, mode Mode) (Rendered, error) {
|
|
return newComposer(in).render(node, mode)
|
|
}
|
|
|
|
// Composing is Resolve, Render and Generators of one node over one composition, which resolves each node of the mesh
|
|
// once for all three (issue 1954: three compositions resolved the mesh again each, 11.7 s for one node's plans). Each
|
|
// answers as it does alone; the first refusal ends it, in that order, with what follows it empty.
|
|
func Composing(in Input, node string, mode Mode) (Resolved, Rendered, map[string]catalogue.Generator, error) {
|
|
c := newComposer(in)
|
|
resolved, err := c.planFor(node)
|
|
if err != nil {
|
|
return Resolved{}, Rendered{}, nil, err
|
|
}
|
|
rendered, err := c.render(node, mode)
|
|
if err != nil {
|
|
return resolved, Rendered{}, nil, err
|
|
}
|
|
gens, err := c.generators()
|
|
if err != nil {
|
|
return resolved, rendered, nil, err
|
|
}
|
|
return resolved, rendered, gens, nil
|
|
}
|
|
|
|
func (c *composer) render(nodeName string, mode Mode) (Rendered, error) {
|
|
resolved, err := c.planFor(nodeName)
|
|
if err != nil {
|
|
return Rendered{}, err
|
|
}
|
|
node, err := c.in.node(nodeName)
|
|
if err != nil {
|
|
return Rendered{}, err
|
|
}
|
|
plan := resolved.Plan
|
|
var out Rendered
|
|
foreseen := func(s string) { out.Foreseen = append(out.Foreseen, s) }
|
|
|
|
grants, withheld, unbound, grantsForeseen, err := c.grantsFor(nodeName)
|
|
if err != nil {
|
|
return Rendered{}, err
|
|
}
|
|
placed, err := c.places(nodeName, resolved)
|
|
if err != nil {
|
|
return Rendered{}, err
|
|
}
|
|
r := placed.Rendering
|
|
out.ForeseenPorts = placed.ForeseenPorts
|
|
// A plan carries its contributions' machine ports, refused where the placement assigned none (issue 774).
|
|
r.Planned = mode == Planning
|
|
r.Grants, r.Withheld, r.Unbound = grants, withheld, unbound
|
|
out.Withheld, out.Unbound = withheld, unbound
|
|
for _, g := range grantsForeseen {
|
|
foreseen(g)
|
|
}
|
|
|
|
// And each module's own secrets: a module handed to the provisioner has its secrets from the vault through the client.
|
|
handing := catalogue.Rendering{Hands: r.Hands, Provisioner: r.Provisioner, Assigned: r.Assigned}
|
|
needed, foreseenOwn, err := c.ownSecretsFor(node, plan, handing, mode)
|
|
if err != nil {
|
|
return Rendered{}, err
|
|
}
|
|
r.Needed = needed
|
|
if mode == Foreseeing || mode == Planning || len(foreseenOwn) > 0 {
|
|
r.Foreseen = foreseenOwn
|
|
}
|
|
var names []string
|
|
for module, secrets := range foreseenOwn {
|
|
for name := range secrets {
|
|
names = append(names, module+"/"+name)
|
|
}
|
|
}
|
|
sort.Strings(names)
|
|
for _, n := range names {
|
|
foreseen(n)
|
|
}
|
|
|
|
// The core's own credentials as the vault sealed them, for an engine that opens the vault's box.
|
|
if node.Stated && node.Contract >= VaultBoxContract {
|
|
sealed, notPlaced, wanted := c.coreBoxesFor(node, plan.Modules, handing)
|
|
r.VaultSealed, r.NotPlaced = sealed, notPlaced
|
|
out.CoreBoxesWanted = wanted
|
|
}
|
|
|
|
// And a certificate for this machine's name inside the mesh, when anything on it asks.
|
|
for _, m := range plan.Modules {
|
|
if m.Certificate == nil {
|
|
continue
|
|
}
|
|
certificate, authority, stood, err := c.certificateFor(node)
|
|
if err != nil {
|
|
return Rendered{}, err
|
|
}
|
|
r.Certificate, r.Authority = certificate, authority
|
|
if stood {
|
|
foreseen("the certificate and the authority of " + nodeName)
|
|
}
|
|
break
|
|
}
|
|
|
|
// The bus's user list, for the machine that runs the bus: names and hashes the Input carries, never passwords.
|
|
if holdsTheBus(plan.Modules) {
|
|
r.BusUsers = c.in.BusUsers
|
|
if r.BusUsers == "" {
|
|
r.BusUsers = ForeseenBusUsers
|
|
foreseen(BusUsersStandIn)
|
|
}
|
|
if len(c.in.BusMissing) > 0 {
|
|
out.Said = append(out.Said, fmt.Sprintf("the bus's user list leaves out %d user(s) the mesh has minted no credential "+
|
|
"for: %s. Each is a user that cannot connect until one is issued", len(c.in.BusMissing), strings.Join(c.in.BusMissing, ", ")))
|
|
}
|
|
}
|
|
r.BusMembership = c.in.BusMemberships[nodeName]
|
|
out.Rendering = r
|
|
// And what the composition found on the way, which the controller writes to its log: a machine not counted as on the network.
|
|
out.Said = c.saidBeside(out.Said)
|
|
return out, nil
|
|
}
|