Read what a machine has from its haves: reported-and-current, the bus list, a given value's first good start; drop the digest of a send (migration 0110) (release 6, issue 1945 B8)

This commit is contained in:
2026-10-24 17:51:40 +02:00
parent 8f90d3ecd6
commit fe3faaef83
29 changed files with 498 additions and 776 deletions
+8 -163
View File
@@ -906,16 +906,6 @@ type BusyWait struct {
// BusyWaiting is the state of a container left running because it said busy (link.BusyWaiting).
const BusyWaiting = "waiting"
// Waiting says whether any container waits because it said busy.
func (d Doing) Waiting() bool {
for _, b := range d.Busy {
if b.State == BusyWaiting {
return true
}
}
return false
}
// FailedResource is one thing a node could not do.
type FailedResource struct {
ID string `json:"id"`
@@ -1143,11 +1133,10 @@ type Reported struct {
Outcome string
// At is when it last reported; nil if it never has.
At *time.Time
// Sent is when the current declaration went to it; nil if nothing ever did.
// Sent is not kept: nothing is sent (ADR 0539 §5). Always nil.
Sent *time.Time
// Current is whether the last report names the declaration last sent — the machine has
// acted on the current words, not merely spoken after they were written. False also covers
// a machine that has not said which, which is every host from before reports carried it.
// Current is whether the machine has everything the assigner wants of it, read from its haves beside the wants
// (cmd/mesh-controller lastReports); false here, as this reads neither.
Current bool
// Declared is the digest of the declaration the last report was about, and ReportedSequence that
// declaration's sequence as the report claimed it (zero from an engine that claims none): what a
@@ -1158,67 +1147,12 @@ type Reported struct {
Busy []BusyWait
}
// SentDeclaration is what one send carried to a machine, as a gate keeps it: the declaration's digest and
// its sequence (novox/hq issue 352). A report about this declaration, or about one sequenced after it, is a
// report on what the gate sent — whatever the machine was sent since.
//
// At is when the controller sent it to that machine: the node's own send time, which is where that machine's
// `apply` duration starts. A walk measures its rest from it, not from its own record of the send, made only after
// every machine of the rest was sent (novox/hq issue 582). Zero for a send kept before it was recorded.
type SentDeclaration struct {
Digest string `json:"digest"`
Sequence int64 `json:"sequence,omitempty"`
At time.Time `json:"at,omitzero"`
// Provisioned is, for each module the send handed to the provisioner, the digest of the templates its plan
// named (novox/hq ADR 0349 §4, ADR 0362): the build the machine's provisioner client reports `prov.applied` on.
// The machine has applied the send of such a module only once both it and the client reported it.
Provisioned map[string]string `json:"provisioned,omitempty"`
}
// ReportsOn says a report is about this send: the declaration itself; one the same machine was sequenced
// after it; or the declaration the machine was sent last (Current), which is this send or a later one —
// sends to a machine are made one after another. A send kept without a sequence is matched by its digest
// and by the last send alone.
func (s SentDeclaration) ReportsOn(r Reported) bool {
if r.Current || (s.Digest != "" && r.Declared == s.Digest) {
return true
}
return s.Sequence > 0 && r.ReportedSequence >= s.Sequence
}
// SentTo is the declaration a machine was last sent, by name: its digest and sequence, and false when it
// was never sent one.
func (i *Inventory) SentTo(ctx context.Context, name string) (SentDeclaration, bool, error) {
var digest *string
var seq *int64
var at *time.Time
var plans []byte
err := i.store.Pool().QueryRow(ctx, `select sent, sequence, sent_at, sent_provisioning from node where name = $1`,
name).Scan(&digest, &seq, &at, &plans)
if errors.Is(err, pgx.ErrNoRows) {
return SentDeclaration{}, false, fmt.Errorf("%w: %s", ErrNoSuchNode, name)
}
if err != nil || digest == nil || *digest == "" {
return SentDeclaration{}, false, err
}
s := SentDeclaration{Digest: *digest}
if seq != nil {
s.Sequence = *seq
}
if at != nil {
s.At = at.UTC()
}
if s.Provisioned, err = provisionedBy(plans); err != nil {
return SentDeclaration{}, false, fmt.Errorf("what %s was sent of the provisioner's plans: %w", name, err)
}
return s, true, nil
}
// LastReports is every machine's last report beside when it was last sent a declaration.
// LastReports is every machine's last report: when, what it did, which declaration it was about and what it holds busy. Sent
// and Current are not kept here: nothing is sent (ADR 0539 §5), and whether a machine has what is wanted of it is read from
// its haves (cmd/mesh-controller lastReports).
func (i *Inventory) LastReports(ctx context.Context) ([]Reported, error) {
rows, err := i.store.Pool().Query(ctx,
`select n.name, coalesce(r.outcome, ''), r.at, n.sent_at,
coalesce(r.declared is not null and r.declared <> '' and r.declared = n.sent, false),
`select n.name, coalesce(r.outcome, ''), r.at,
coalesce(r.declared, ''), coalesce(r.reported_sequence, 0), coalesce(r.busy, '[]')
from node n left join node_report r on r.node = n.id
order by n.name`)
@@ -1230,8 +1164,7 @@ func (i *Inventory) LastReports(ctx context.Context) ([]Reported, error) {
for rows.Next() {
var r Reported
var busy []byte
if err := rows.Scan(&r.Node, &r.Outcome, &r.At, &r.Sent, &r.Current, &r.Declared, &r.ReportedSequence,
&busy); err != nil {
if err := rows.Scan(&r.Node, &r.Outcome, &r.At, &r.Declared, &r.ReportedSequence, &busy); err != nil {
return nil, err
}
if err := json.Unmarshal(busy, &r.Busy); err != nil {
@@ -1242,94 +1175,6 @@ func (i *Inventory) LastReports(ctx context.Context) ([]Reported, error) {
return out, rows.Err()
}
// AwaitingSince is since when a machine has had something waiting for its next send (novox/hq issue
// 275): the oldest assignment on it made after it was last sent, or — when nothing was assigned since —
// when it was last sent, or when it joined if it never was. The moment a bound on "not pushed yet" is
// read from: every change a push carries happened after the last push, and an assignment is the one
// that says when.
func (i *Inventory) AwaitingSince(ctx context.Context, name string) (time.Time, error) {
return i.chainAwaitingSince(ctx, name)
}
// RecordSentBusUsers keeps a digest of the bus's user list a machine was just sent, by its name
// (novox/hq issue 249): whether the machine holding the bus must go first is whether this differs
// from the list composed now.
func (i *Inventory) RecordSentBusUsers(ctx context.Context, name, digest string) error {
_, err := i.store.Pool().Exec(ctx,
`update node set sent_bus_users = $2 where name = $1`, name, digest)
return err
}
// SentBusUsers is the digest of the bus's user list a machine was last sent, empty for none.
func (i *Inventory) SentBusUsers(ctx context.Context, name string) (string, error) {
var sent string
err := i.store.Pool().QueryRow(ctx,
`select sent_bus_users from node where name = $1`, name).Scan(&sent)
if errors.Is(err, pgx.ErrNoRows) {
return "", nil
}
return sent, err
}
// Outstanding is the digest of the declaration a machine was last sent, by its name, and empty
// for one that has never been sent anything.
//
// **By name rather than by id**, because the caller is the serving loop and what a node puts in a
// report is its name. Asked of one machine rather than read from Waiting's sweep, because it is
// asked per message: a report names the declaration it is about, and a report about one the mesh
// has already moved past is not acted on (design 25 §3).
func (i *Inventory) Outstanding(ctx context.Context, name string) (string, error) {
var sent string
err := i.store.Pool().QueryRow(ctx,
`select coalesce(sent, '') from node where name = $1`, name).Scan(&sent)
if errors.Is(err, pgx.ErrNoRows) {
// Not an error worth carrying up: a report from a machine the mesh has no record of has
// nothing to be stale against, and whatever is wrong with it is the listener's to say.
return "", nil
}
return sent, err
}
// Waiting is every machine whose declaration has changed since it was last sent one.
//
// The caller works out what each machine should be now, because only it can — resolution is the
// control plane's and this context holds records. What is answered here is the comparison.
//
// **A machine that has never been sent anything is waiting**, and says so differently: it is not
// out of date, it has never been told, and the remedy is the same push while the situation is not
// the same at all.
func (i *Inventory) Waiting(ctx context.Context, would map[string]string) ([]Machine, error) {
rows, err := i.store.Pool().Query(ctx,
`select name, coalesce(sent, ''), sent_at from node order by name`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []Machine
for rows.Next() {
var m Machine
var at *time.Time
if err := rows.Scan(&m.Node, &m.Sent, &at); err != nil {
return nil, err
}
m.SentAt = at
wanted, known := would[m.Node]
if !known {
// Nothing was computed for it — it resolves to nothing, or the caller did not ask.
// Silence rather than a guess: saying "waiting" about a machine nobody worked out
// would be inventing a comparison.
continue
}
if m.Sent == wanted {
continue
}
m.Never = m.Sent == ""
out = append(out, m)
}
return out, rows.Err()
}
// Machine is one machine that has not been sent what it should be.
type Machine struct {
Node string