grantsFor granted every consumer a pair credential from the provider was ever made for, so a consumer pinned back to its own store was still asked of the store it left, which then never retired it. A credential whose consumer's resolution binds it elsewhere is now withdrawn like one nobody asks for, kept on record for the login the provider keeps, and said on plan and push.
148 lines
6.7 KiB
Go
148 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// sendable is a declaration as a machine is sent it: its resources and, for an adopted node, its
|
|
// mode and which modules were taken on it (novox/hq ADR 0100).
|
|
//
|
|
// **Body is the only place the envelope is marshalled.** It was written by hand at every send site,
|
|
// in the digest the mesh compares, and in `plan --json`; a key added at one and not another would
|
|
// make a machine look out of date for ever, or send something `plan` never showed.
|
|
type sendable struct {
|
|
Resources []map[string]any
|
|
// Sequence orders this send against every other to the same node: one higher each time, taken
|
|
// under the node's hold just before the body is made (novox/hq 04-ISSUES/107). Zero is not sent
|
|
// at all, which a host reads as "no order claimed" — the shape of every declaration before this.
|
|
Sequence int64
|
|
// Epoch is the controller lease's epoch it was composed under (novox/hq to-be 45 §6): a machine that
|
|
// heard a later epoch refuses it. Zero is not sent at all — every machine whose node-engine has not
|
|
// said it reads one is sent none, because an older node-engine refuses a key it does not know, whole
|
|
// (link/order.go, the contract).
|
|
Epoch uint64
|
|
// Adoption is nil for a converged node, and then the body is byte for byte what it was before
|
|
// adoption existed: an older host parses the envelope strictly and would refuse the key.
|
|
Adoption *adoptionEnvelope
|
|
|
|
// Received and Mesh are not sent in the declaration. They are what this machine's memberships
|
|
// are issued with on the bus (novox/hq ADR 0167): each module's received contributions, from
|
|
// the same composition as its received files, and every machine's private-network address.
|
|
Received map[string]map[string][]catalogue.Contribution
|
|
Mesh []string
|
|
// BusUsers is the bus's user list this declaration carries, empty for every machine but the one
|
|
// holding the bus; not sent apart from the file it is in. Its digest is recorded once sent, so
|
|
// whether that machine must go first is read from the list alone (novox/hq issue 249).
|
|
BusUsers string
|
|
// LeftOut is every module of the machine's set left out of this declaration because a stored
|
|
// setting cannot compose with its definition (novox/hq ADR 0163, rule 6), sorted. The host
|
|
// keeps that module's held things and touches none of its containers; a machine is told
|
|
// everything or nothing about what it IS told, and what it is not told is said. Absent from
|
|
// the body when empty, so a declaration that leaves nothing out is byte for byte what it was.
|
|
LeftOut []string
|
|
// leftOutWhy is why each was, for push and plan to say; never on the wire.
|
|
leftOutWhy map[string]string
|
|
// withheld is every consumer this machine's grants leave out, because its identity overflows the
|
|
// provision's bound (novox/hq ADR 0225); for push and plan to say, never on the wire.
|
|
withheld []catalogue.Overflow
|
|
// unbound is every consumer whose credential from this machine is on record and that is bound
|
|
// elsewhere (novox/hq issue 274); for push and plan to say, never on the wire.
|
|
unbound []catalogue.Unbound
|
|
// Builds is the build of each module this declaration carries — module to the commit its build
|
|
// was made from — recorded with the send and never on the wire (novox/hq issue 259, ADR 0221).
|
|
// Composed only on the send path; nil records that it is not known.
|
|
Builds map[string]string
|
|
// Bindings is where each consumer of a provision that keeps its data is bound in this declaration
|
|
// (novox/hq ADR 0232), recorded with the send and never on the wire. Composed only on the send path.
|
|
Bindings []inventory.Binding
|
|
}
|
|
|
|
// adoptionEnvelope is what an adopted node is told about its mode. Taken is every module taken on
|
|
// it that it runs; Untaken is, for every module it runs that is not taken, the ids of every one of
|
|
// that module's resources — what the host keeps as found until the module is taken (ADR 0103). Ids
|
|
// rather than a rule to split them by, because a module's name may contain a dot.
|
|
type adoptionEnvelope struct {
|
|
Taken []string `json:"taken"`
|
|
Untaken map[string][]string `json:"untaken,omitempty"`
|
|
}
|
|
|
|
// Body is the declaration's bytes, as sent and as digested.
|
|
func (s sendable) Body() ([]byte, error) {
|
|
envelope := map[string]any{"declaration": 1, "resources": s.Resources}
|
|
if s.Adoption != nil {
|
|
envelope["adoption"] = s.Adoption
|
|
}
|
|
if s.Sequence > 0 {
|
|
envelope["sequence"] = s.Sequence
|
|
}
|
|
if s.Epoch > 0 {
|
|
envelope["epoch"] = s.Epoch
|
|
}
|
|
if len(s.LeftOut) > 0 {
|
|
envelope["left_out"] = s.LeftOut
|
|
}
|
|
// An empty declaration is deliberate here — the node owns nothing the mesh put there
|
|
// (novox/hq issue 127) — and the host refuses an empty body unless it is told the emptiness
|
|
// is meant, so a truncated or mis-composed body is never mistaken for "own nothing".
|
|
if len(s.Resources) == 0 {
|
|
envelope["owns_nothing"] = true
|
|
}
|
|
return json.Marshal(envelope)
|
|
}
|
|
|
|
// adoptionOf is the envelope for a node, nil when it is converged.
|
|
func adoptionOf(ctx context.Context, inv *inventory.Inventory, record inventory.Node,
|
|
plan catalogue.Resolution, composed catalogue.Composed) (*adoptionEnvelope, error) {
|
|
if !record.Adopted {
|
|
return nil, nil
|
|
}
|
|
taken, err := inv.Taken(ctx, record.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return adoptionFor(plan, taken, composed), nil
|
|
}
|
|
|
|
// adoptionFor is the envelope computed from what was taken and who owns each resource.
|
|
//
|
|
// Every module the node runs that is not taken is untaken — including one pulled in by another
|
|
// rather than assigned: what is found is kept until its module is taken, whoever put it there.
|
|
func adoptionFor(plan catalogue.Resolution, taken []string,
|
|
composed catalogue.Composed) *adoptionEnvelope {
|
|
isTaken := map[string]bool{}
|
|
for _, m := range taken {
|
|
isTaken[m] = true
|
|
}
|
|
out := &adoptionEnvelope{Taken: []string{}}
|
|
runs := map[string]bool{}
|
|
for _, m := range plan.Modules {
|
|
runs[m.Module] = true
|
|
if isTaken[m.Module] {
|
|
out.Taken = append(out.Taken, m.Module)
|
|
}
|
|
}
|
|
sort.Strings(out.Taken)
|
|
for _, r := range composed.Resources {
|
|
id, _ := r["id"].(string)
|
|
module, owned := composed.Owner[id]
|
|
// Every kind, not only files and containers (novox/hq ADR 0103): a directory, a service, a
|
|
// container mounting what was found and an action run in a held container all reach what
|
|
// the machine already has. What the mesh declares of its own is never held.
|
|
if !owned || !runs[module] || isTaken[module] ||
|
|
strings.HasPrefix(id, catalogue.AdoptionPrefix) {
|
|
continue
|
|
}
|
|
if out.Untaken == nil {
|
|
out.Untaken = map[string][]string{}
|
|
}
|
|
out.Untaken[module] = append(out.Untaken[module], id)
|
|
}
|
|
return out
|
|
}
|