Two controllers could both act (issue 204), a reconcile's report could overtake the apply after it and the digest decided (issue 267), and a grant could make a second writer of a machine's report. - The lease (internal/lease, ADR 0229): mesh-controller_lease key `holder`, 15 s age, renewed every 5 s by compare-and-set; the epoch is the revision it was taken at. The gate is the clock (stops 3 s before expiry); a refused renewal is a loss and the process exits; a holder that stops gives it back. serve takes it before asserting the bus. Epochs kept in the store (migration 0068 controller_epoch) as a floor: a bucket raised from nothing is compacted past it. Unleased (no epoch, S12 urgent) only when nobody holds it and the bus will not let it be written. A shell command acts under the holder's epoch, or its own lease when none. - Declarations carry `epoch` inside the signed envelope, only to a machine whose latest account carried a report_sequence (mesh-host #35); would-send is composed with the epoch last sent. Allot and the send both pass the gate. - Reports: contract in internal/link/order.go (epoch, sequence, report_sequence, older_than, refused_older). Accounts kept by epoch, then sequence, then report sequence; older refused, counted; unordered reports keep the digest rule. Plans by compare-and-set on a revision, with epoch. Conditions and calls carry the epoch and are not written off the lease. - S12 and S13 (naming the writer by epoch) watched, D5 run; reset of the bucket said. Writers table compiled in and enforced in PermissionsFor; the controller no longer publishes mesh.control.>. A contract per consumed kind, and the empty-on-error lint over the repository. - mesh-host pinned to its main with the epoch in the validator (D1 validates the envelope as sent). Needs mesh-host's genesis lock with the lease grant (mesh-host PR) for TestTheInstallersFirstUserListIsWhatTheControllerWouldCompose.
119 lines
6.3 KiB
Go
119 lines
6.3 KiB
Go
package link
|
|
|
|
import "strconv"
|
|
|
|
// The order a declaration carries, and the order a report about one carries back (novox/hq to-be 45
|
|
// §6, ADR 0227 rule 2).
|
|
//
|
|
// **This file is the controller's side of the contract with the node-engine for Phase 2**, and the one
|
|
// place it is written here. It mirrors mesh-host internal/link/messages.go (`Order`, and the report's
|
|
// `report_sequence`, `older_than`, `refused_older`) field for field and rule for rule, as Report has
|
|
// always mirrored the host's report: a key added on one side and not the other is a key the other side
|
|
// does not know exists.
|
|
//
|
|
// **The wire.**
|
|
//
|
|
// - A declaration's order is two top-level keys of its signed envelope, beside "declaration" and
|
|
// "resources": `sequence` (since issue 107) and `epoch` (new). Inside the signed bytes, so a message
|
|
// cannot be given a newer order than the controller gave it.
|
|
// - A report carries the order of the declaration it is about as the same two top-level keys, beside
|
|
// "declared"; `report_sequence`, the node-engine's own number for the report; `older_than`, on a
|
|
// refusal of a declaration older than one it applied, the order of the one it holds; and
|
|
// `refused_older`, how many it has refused so, ever, on every report.
|
|
//
|
|
// Every key is optional and absent when zero, and zero is "no order claimed", never "first". So each
|
|
// side reads the other's older shape:
|
|
//
|
|
// - **An older node-engine with this controller.** It decodes a declaration strictly and refuses a key
|
|
// it does not know, whole — so `epoch` is sent only to a machine whose latest report carried a
|
|
// `report_sequence`, which a node-engine that reads the epoch always does. Until then it is sent the
|
|
// sequence alone, as today; its reports carry no report sequence and are judged by the digest they
|
|
// name, as today (issue 267).
|
|
// - **A newer node-engine with an older controller.** It is sent no epoch, which claims none: it
|
|
// refuses nothing by epoch. The keys it adds to a report are fields the older controller ignores.
|
|
// - **A controller rolled back to a build without the lease** sends no epoch again and is not refused
|
|
// for it: a node-engine refuses by epoch only when both declarations claim one. That gives up the
|
|
// epoch's protection while such a build runs — the price of a rollback that cannot strand every
|
|
// machine.
|
|
//
|
|
// **Epoch** is the controller lease's epoch (to-be 45 §6): the lease bucket's revision at which the
|
|
// instance that composed the declaration took the lease. It only grows: a later holder's is higher, and
|
|
// the controller never issues one at or under the highest it has issued (internal/lease, the floor).
|
|
// **Sequence** is the declaration's number for that machine, one higher every send, taken from the
|
|
// controller's store before the declaration is composed (issues 107, 204).
|
|
|
|
// Order is where a declaration stands among everything the mesh has sent a machine: the lease epoch it
|
|
// was sent under and its sequence. Zero in either claims none.
|
|
type Order struct {
|
|
Epoch int64 `json:"epoch,omitempty"`
|
|
Sequence int64 `json:"sequence,omitempty"`
|
|
}
|
|
|
|
// Older is the node-engine's rule, as mesh-host states it: a declaration of this order is older than
|
|
// one of order `than` the machine applied **only when both claim an epoch** — and then when its epoch
|
|
// is lower, or its epoch is the same and its sequence lower (both claimed). A higher epoch is a new lease
|
|
// holder and is never older, whatever its sequence. Here so the controller's tests state what the
|
|
// machines will do with what it sends.
|
|
func (o Order) Older(than Order) bool {
|
|
if o.Epoch <= 0 || than.Epoch <= 0 {
|
|
return false
|
|
}
|
|
if o.Epoch != than.Epoch {
|
|
return o.Epoch < than.Epoch
|
|
}
|
|
return o.Sequence > 0 && than.Sequence > 0 && o.Sequence < than.Sequence
|
|
}
|
|
|
|
// Words is an order as a person reads it in a log line. Not String: Report embeds Order, and a Stringer
|
|
// promoted onto every report would print each one as its order alone.
|
|
func (o Order) Words() string {
|
|
switch {
|
|
case o.Epoch > 0:
|
|
return "epoch " + strconv.FormatInt(o.Epoch, 10) + ", sequence " + strconv.FormatInt(o.Sequence, 10)
|
|
case o.Sequence > 0:
|
|
return "sequence " + strconv.FormatInt(o.Sequence, 10) + ", no epoch"
|
|
}
|
|
return "no order"
|
|
}
|
|
|
|
// Account is the order of one account of a machine — a report about a declaration: that declaration's
|
|
// order and the node-engine's own number for the report.
|
|
type Account struct {
|
|
Order
|
|
ReportSequence int64
|
|
}
|
|
|
|
// AccountOf is a report's account order.
|
|
func AccountOf(r Report) Account { return Account{Order: r.Order, ReportSequence: r.ReportSequence} }
|
|
|
|
// Ordered says a report comes from a node-engine that orders its reports: it carries a report sequence.
|
|
// Such a report is judged by its order (OlderThan); one without is judged by the digest it names, as
|
|
// before (issue 267).
|
|
func (r Report) Ordered() bool { return r.ReportSequence > 0 }
|
|
|
|
// ReadsEpoch says the node-engine that made the report takes an epoch in a declaration: one that orders
|
|
// its reports does (mesh-host: "its presence also says this host reads a declaration's epoch").
|
|
func (r Report) ReadsEpoch() bool { return r.ReportSequence > 0 }
|
|
|
|
// StaleRefusalOf says a report refuses a declaration older than one the machine applied: the node-engine
|
|
// names the order it holds (`older_than`), or, from a node-engine older than that, says so in the words
|
|
// of its sequence refusal.
|
|
func (r Report) StaleRefusalOf() bool {
|
|
return r.OlderThan != nil || (r.Refused != "" && IsStaleRefusal(r.Refused))
|
|
}
|
|
|
|
// OlderThan is the controller's rule (to-be 45 §6): whether this account is older than the one kept for
|
|
// the machine — **by epoch, then sequence, then report sequence**, each compared only where both claim
|
|
// one. An account of a declaration of an older epoch is refused; of an older sequence, refused; an older
|
|
// report of the same declaration (a reconcile's, overtaken by the apply's — issue 267), refused. Equal is
|
|
// the same report again, redelivered, and is not refused.
|
|
func (a Account) OlderThan(kept Account) bool {
|
|
if a.Epoch > 0 && kept.Epoch > 0 && a.Epoch != kept.Epoch {
|
|
return a.Epoch < kept.Epoch
|
|
}
|
|
if a.Sequence > 0 && kept.Sequence > 0 && a.Sequence != kept.Sequence {
|
|
return a.Sequence < kept.Sequence
|
|
}
|
|
return a.ReportSequence > 0 && kept.ReportSequence > 0 && a.ReportSequence < kept.ReportSequence
|
|
}
|