package compose import ( "errors" "fmt" "sort" "git.novox.be/novox/mesh-sdk/go/catalogue" ) // notResolvable marks the one failure in a node's resolution that is a statement about the node: its assigned modules // do not compose. Every other failure means the mesh could not be asked. Whoever gathers across the mesh skips a node // that cannot resolve and never a node that could not be read (novox/hq 04-ISSUES/152). type notResolvable struct{ err error } func (n notResolvable) Error() string { return n.err.Error() } func (n notResolvable) Unwrap() error { return n.err } // unresolvable reports whether err is a node's own set failing to compose. func unresolvable(err error) bool { var n notResolvable return errors.As(err, &n) } // Passes is what a composition found that the mesh must record, and did not: each seat held once whose holder on record // is assigned nowhere that exactly one claimant takes over (novox/hq ADR 0449 clause 4), and the lines that say what was // found. The caller records a pass and writes its hand act; this package does neither. type Passes struct { // Passes are the seats to pass, in the order they were found. Passes []catalogue.SeatPass // Said are the lines the finding says (a seat two claimants could take, one nothing can). Said []string } // LeftOut is a node that does not resolve in the view of the rest of the mesh, with why: it offers nothing and holds // nothing as far as every other machine's plan can tell (novox/hq issue 188). type LeftOut struct { Node string Err error } // String is the line the controller says of it. func (l LeftOut) String() string { return fmt.Sprintf("%s is left out of the rest of the mesh: it does not resolve: %v", l.Node, l.Err) } // TheRestOfTheMesh is what every other node holds and offers, as the controller works it out (plan.go theRestOfTheMesh): // two passes over the others, because what a node offers needs it resolved and resolving it may need what the mesh // offers. The first takes brokered requirements on trust and answers only what each node offers; the second answers // everything with that in hand. Nothing is declared from the first. // // It writes nothing. A seat the mesh passes on is returned in Passes for the caller to record; the World carries the // holdings as they stand after the passes, and Input's own are unchanged. A node left out is returned with its reason, in // the order the controller said them (the first pass's, then the second's when no node is excluded). func TheRestOfTheMesh(in Input, exclude string) (catalogue.World, Passes, []LeftOut, error) { c := newComposer(in) return c.theRestOfTheMesh(exclude) } func (c *composer) theRestOfTheMesh(exclude string) (catalogue.World, Passes, []LeftOut, error) { shelf := c.in.Catalogue holdings := append([]catalogue.Held(nil), c.in.Holdings...) nodes := c.in.sorted() onNetwork, err := c.whereEveryoneIs() if err != nil { return catalogue.World{}, Passes{}, nil, err } type candidate struct { node catalogue.Node assigned []string pins map[string]catalogue.Chosen shelf map[string]catalogue.Manifest } var others []candidate // Every machine's assignments, before the holdings are read by any resolution (novox/hq ADR 0449 clause 4): a seat // held once whose holder on record is assigned on no machine passes to its only claimant here. everyAssigned := map[string][]string{} for _, n := range nodes { if len(n.Assigned) > 0 { everyAssigned[n.Name] = n.Assigned } } var passes Passes holdings, passes = passSeatsOn(holdings, assignmentsOf(shelf, everyAssigned)) for _, n := range nodes { if n.Name == exclude { continue } theirs := everyAssigned[n.Name] if len(theirs) == 0 { continue } others = append(others, candidate{ node: catalogue.Node{Name: n.Name, Site: n.Place.Site, Capabilities: n.Capabilities, At: onNetwork[n.Name], PublicDomain: n.PublicDomain}, assigned: theirs, pins: n.Pins, shelf: c.shelfFor(n)}) } var left []LeftOut offered := map[string][]catalogue.Provider{} var firstHeld []catalogue.Held for _, o := range others { got, err := catalogue.Resolve(o.shelf, o.assigned, o.node, catalogue.World{Unchecked: true, Holdings: holdings}) if err != nil { // Said, not skipped: a machine dropped here offers nothing and holds nothing. left = append(left, LeftOut{Node: o.node.Name, Err: err}) continue } firstHeld = append(firstHeld, got.Claims...) for _, m := range got.Modules { for _, name := range m.OffersAt(catalogue.ScopeMesh) { // What that module says a consumer needs to know, with that node's settings on it. serves, layers, err := c.servedOnNode(o.node.Name, m, name) if err != nil { return catalogue.World{}, Passes{}, nil, err } // And its own composed names, as that node composes them (novox/hq ADR 0346). serves, leftOut, err := got.ServedOwnNames(m, name, serves, catalogue.WithDefaults(m, layers)) if err != nil { return catalogue.World{}, Passes{}, nil, err } offered[name] = append(offered[name], catalogue.Provider{ Node: o.node.Name, At: o.node.At, Serves: serves, Module: m.Module, LeftOut: leftOut}) } } } for k := range offered { sort.Slice(offered[k], func(i, j int) bool { return offered[k][i].Node < offered[k][j].Node }) } // The second pass is given the first pass's holdings (novox/hq ADR 0110). world := catalogue.World{Offered: offered, Held: firstHeld, Holdings: holdings} var held []catalogue.Held for _, o := range others { // Each machine is resolved with its own pins, as its plan is. theirs := world theirs.Pinned = o.pins got, err := catalogue.Resolve(o.shelf, o.assigned, o.node, theirs) if err != nil { // Said only for the whole-mesh view: with one machine excluded, the view is partial by design. if exclude == "" { left = append(left, LeftOut{Node: o.node.Name, Err: err}) } continue } held = append(held, got.Claims...) } world.Held = held return world, passes, left, nil } // passSeatsOn finds, in the composition that finds it, the new holder of each seat held once whose holder on record is // assigned nowhere and which has exactly one claimant able to hold it (novox/hq ADR 0449 clause 4), and answers the // holdings as they are after it, with the passes. Nothing is recorded. func passSeatsOn(holdings []catalogue.Held, assignments []catalogue.Assignment) ([]catalogue.Held, Passes) { passes, said := catalogue.SeatsToPassOn(holdings, assignments) out := Passes{Said: said} for _, p := range passes { out.Passes = append(out.Passes, p) var next []catalogue.Held for _, h := range holdings { if hs, ok := catalogue.SeatNamed(h.Claim); ok && hs.Name == p.Seat.Name { continue } next = append(next, h) } p.To.Site = p.From.Site holdings = append(next, p.To) } return holdings, out } // assignmentsOf are the assignments of every machine, as the passing of a seat reads them. A module assigned and // missing from the shelf is still assigned: an assignment with no claims, so its holder is not taken for gone. func assignmentsOf(shelf map[string]catalogue.Manifest, assigned map[string][]string) []catalogue.Assignment { var out []catalogue.Assignment names := make([]string, 0, len(assigned)) for node := range assigned { names = append(names, node) } sort.Strings(names) for _, node := range names { for _, module := range assigned[node] { m, ok := shelf[module] if !ok { m = catalogue.Manifest{Module: module} } out = append(out, catalogue.Assignment{Node: node, Manifest: m}) } } return out } // servedOnNode is what a module on a node tells a consumer of one of its provisions, with THAT node's ports on it: the // port the node was given over the one the mesh assigned over the manifest's own, settled with the node's settings // layers. The one derivation for every reader of a provider's address. func (c *composer) servedOnNode(node string, m catalogue.Manifest, provision string) (map[string]any, []catalogue.Layer, error) { ports, layers, err := c.portsGivenOn(node, m) if err != nil { return nil, nil, err } serves := catalogue.ServedOn(m, provision, ports) if len(serves) > 0 { serves, err = catalogue.Settle(serves, catalogue.WithDefaults(m, layers)) if err != nil { return nil, nil, err } } return serves, layers, nil } // portsGivenOn is where a node puts a module's ports — assigned, then given over them — and the node's settings layers // for the module. func (c *composer) portsGivenOn(node string, m catalogue.Manifest) (map[int]int, []catalogue.Layer, error) { n, err := c.in.node(node) if err != nil { return nil, nil, err } ports := map[int]int{} for wanted, at := range n.Ports[m.Module] { ports[wanted] = at } layers := n.Settings[m.Module] if given, err := catalogue.GivenPorts(m, layers); err == nil { for wanted, at := range given { ports[wanted] = at } } return ports, layers, nil }