A node that reports file-references gets the digest and size of each file over 64 KiB that may travel so, and the content is put in the bucket declaration-files before the declaration is sent; others get every content inline. The controller alone writes the bucket and node-engines read an object by name (the four grants the node-engine's own fetch makes, no list, no write). An object goes a day after no node's latest declaration names it. The near-limit condition warns at 75 percent, and a test holds the control node's declaration, in the reference form, under half the bus's largest message.
192 lines
9.7 KiB
Go
192 lines
9.7 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/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
|
|
// Generation is the assignment generation it was composed from (novox/hq issue 234): a machine that
|
|
// applied a later one refuses it, so a send composed from a view of the assignments the mesh has moved
|
|
// past cannot undeclare what is still assigned. Zero is not sent at all — every machine whose
|
|
// node-engine has not said it reads one is sent none, for the reason the epoch is not (an older
|
|
// node-engine refuses a key it does not know, whole).
|
|
Generation int64
|
|
// composedFrom is the generation read before this declaration was composed, and actingEpoch the
|
|
// lease epoch its sender acted under — whether or not the machine is sent either — for the record of
|
|
// the send; never on the wire.
|
|
composedFrom int64
|
|
actingEpoch uint64
|
|
// ReadsModules says the machine's node-engine reads a module per resource and a build per module (novox/hq issue
|
|
// 1145): only then are they sent, because an older node-engine refuses a key it does not know, whole.
|
|
ReadsModules bool
|
|
// fileReferences says the machine's node-engine reports the capability of reading a file given as a digest and a size
|
|
// (declaration.FileReferencesCapability, novox/hq ADR 0486 §5): then every file over 64 KiB that may travel so does (Body,
|
|
// objects). Not on the wire as a field; the bodies differ.
|
|
fileReferences bool
|
|
// moduleOf is the composer's owner of each resource id, and buildIDs the build each module carries (its id):
|
|
// kept beside the resources, and on the wire only to a machine that reads them.
|
|
moduleOf map[string]string
|
|
buildIDs map[string]string
|
|
// modules are the modules this declaration names, for the record of the send; never on the wire.
|
|
modules []string
|
|
// localNetwork is every port admitted from the local network and what it resolved to (novox/hq ADR
|
|
// 0340 §8), for plan to say; never on the wire.
|
|
localNetwork []catalogue.LocalNetworkPort
|
|
// 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
|
|
// foreseen is every own secret composed with a stand-in, as `module/name`: what the next send will
|
|
// make (Foreseeing, novox/hq issue 275). Never on the wire, and a declaration that has any is never
|
|
// sent.
|
|
foreseen []string
|
|
// unplaced is every contribution its holder's template could not render, naming its module and
|
|
// why (novox/hq ADR 0255): left out of this declaration, for push and plan to say, never on the wire.
|
|
unplaced []string
|
|
// handedOnly is each file left out because only the provisioner's client fills it and the module is not handed
|
|
// here (a provider's consumers file, ADR 0373).
|
|
handedOnly []string
|
|
// 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
|
|
// Filled is the path of every file this declaration hands to the provisioner, and Provisioner the account the
|
|
// client that fills them runs as (novox/hq ADR 0349 §4, §5, design 57 P6): on the wire as `filled_files` and
|
|
// `provisioner_account`, both absent when nothing is handed, so every other declaration is byte for byte what
|
|
// it was. plans is each handed module's plan, kept with the send for the assignments mirror; never on the wire.
|
|
Filled []string
|
|
Provisioner string
|
|
plans map[string]catalogue.Plan
|
|
// requiredBy is, for each handed module only pulled in by another, the module that requires it (issue 765): kept
|
|
// beside its plan, so the assignments mirror gives the plan a value of its own. Never on the wire.
|
|
requiredBy map[string]string
|
|
}
|
|
|
|
// 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) {
|
|
resources, _, err := s.byReference()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
envelope := map[string]any{"declaration": 1, "resources": resources}
|
|
if s.ReadsModules {
|
|
// The module of each resource the composer owns (what the mesh adds of its own has none), and the build of each
|
|
// module (novox/hq issue 1145): to a machine that said it reads them, and no other.
|
|
named := make([]map[string]any, len(resources))
|
|
for n, r := range resources {
|
|
module, owned := s.moduleOf[fmt.Sprint(r["id"])]
|
|
if !owned {
|
|
named[n] = r
|
|
continue
|
|
}
|
|
with := make(map[string]any, len(r)+1)
|
|
for k, v := range r {
|
|
with[k] = v
|
|
}
|
|
with["module"] = module
|
|
named[n] = with
|
|
}
|
|
envelope["resources"] = named
|
|
if len(s.buildIDs) > 0 {
|
|
envelope["builds"] = s.buildIDs
|
|
}
|
|
}
|
|
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
|
|
}
|
|
if s.Generation > 0 {
|
|
envelope["generation"] = s.Generation
|
|
}
|
|
if len(s.Filled) > 0 {
|
|
envelope["filled_files"] = s.Filled
|
|
envelope["provisioner_account"] = s.Provisioner
|
|
}
|
|
// 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)
|
|
}
|
|
|
|
// byReference is the resources as the machine is sent them: with the contents of its large files replaced by their digests when
|
|
// it reports it reads them (issue 1696), and the contents it replaced, by the name of the object that holds each.
|
|
func (s sendable) byReference() ([]map[string]any, map[string][]byte, error) {
|
|
if !s.fileReferences {
|
|
return s.Resources, nil, nil
|
|
}
|
|
return referencing(s.Resources)
|
|
}
|
|
|
|
// objects are the contents the body names by digest, by the name of each one's object: what is put in the bucket before the
|
|
// body is sent. Empty for a machine that is sent every content inline.
|
|
func (s sendable) objects() (map[string][]byte, error) {
|
|
_, objects, err := s.byReference()
|
|
return objects, err
|
|
}
|