Ask the backup seats before the first tier (issue 1065)
This commit is contained in:
@@ -245,7 +245,8 @@ var VerbsTheSelfCheckAsks = []SeatVerb{{Seat: "node-intrusion-prevention", Verb:
|
||||
// VerbsTheBusStepAsks are the seat verbs the bus's planned step calls (novox/hq to-be 45 §8, ADR 0236):
|
||||
// the bus machine's backup holder takes the bus's snapshot now, before the bus is replaced (ADR 0235's
|
||||
// dump), and says when it is taken (`backed-up`, granted with the self-check's). The one verb of
|
||||
// the controller's grant that acts, and only through the step a person starts.
|
||||
// the controller's grant that acts: through the step a person starts, and through the backups a delivery plan
|
||||
// takes before it asks its first tier (novox/hq ADR 0404).
|
||||
var VerbsTheBusStepAsks = []SeatVerb{{Seat: "node-backup", Verb: "now"}}
|
||||
|
||||
// VerbsTheControllerAsksForASecret are the seat verbs `give` calls (novox/hq ADR 0259 §10): the operator's
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The plan's backups are the contract mesh-delivery reads from `plan-moved` (issue 1065): their JSON field names
|
||||
// are fixed here, and a plan keeps them through a round trip.
|
||||
func TestAPlanKeepsItsBackupsThroughItsJSONUnderTheNamesTheDeliveryReads(t *testing.T) {
|
||||
at := time.Date(2026, 10, 17, 12, 0, 0, 0, time.UTC)
|
||||
p := Plan{ID: "p", Backups: &PlanBackups{State: BackupFailed, NoHolder: []string{"bare"},
|
||||
Asked: []PlanBackup{{Machine: "ace", Module: "mail", State: BackupFailed, AskedAt: &at, Words: "no space left"}}}}
|
||||
raw, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, name := range []string{`"backups":{"state":"failed","asked":[{"machine":"ace","module":"mail","state":"failed",` +
|
||||
`"asked_at":"2026-10-17T12:00:00Z","words":"no space left"}],"no_holder":["bare"]}`} {
|
||||
if !strings.Contains(string(raw), name) {
|
||||
t.Fatalf("plan JSON %s lacks %s", raw, name)
|
||||
}
|
||||
}
|
||||
var back Plan
|
||||
if err := json.Unmarshal(raw, &back); err != nil || !reflect.DeepEqual(back.Backups, p.Backups) {
|
||||
t.Fatalf("round trip: %v %+v", err, back.Backups)
|
||||
}
|
||||
raw, _ = json.Marshal(Plan{ID: "q"})
|
||||
if strings.Contains(string(raw), "backups") {
|
||||
t.Fatalf("a plan with no backups says none: %s", raw)
|
||||
}
|
||||
}
|
||||
@@ -62,12 +62,48 @@ type Plan struct {
|
||||
// Phases is the walk's time from its merge, phase by phase (ADR 0282 decision 6): never kept, worked out
|
||||
// from the walk's own moments by whoever says the walk (`delivery walks`, `plan-moved`).
|
||||
Phases *WalkPhases `json:"phases,omitempty"`
|
||||
// Backups is the backups the walk takes before it asks its first tier (novox/hq ADR 0404 decisions 2 and 3):
|
||||
// per machine and module, what was asked and how it went, and the machines with no backup holder. Nil for a
|
||||
// walk that moves no module with an item protected by a backup, and for a walk kept before it was.
|
||||
Backups *PlanBackups `json:"backups,omitempty"`
|
||||
// WalkSaid is, on the save that started or ended the walk, which of the two it was (novox/hq ADR 0308
|
||||
// decision 1): set by the save, never kept, read by whoever says the save (`plan-moved`). Empty on every
|
||||
// other save.
|
||||
WalkSaid string `json:"-"`
|
||||
}
|
||||
|
||||
// The states of a plan's backups, as a whole and each.
|
||||
const (
|
||||
BackupPending = "pending"
|
||||
BackupDone = "done"
|
||||
BackupFailed = "failed"
|
||||
)
|
||||
|
||||
// PlanBackups is a walk's record of the backups it takes before it asks anything (novox/hq ADR 0404): read by
|
||||
// mesh-delivery from `plan-moved`, so its JSON field names are a contract between the two repositories.
|
||||
type PlanBackups struct {
|
||||
// State is the whole: pending while any backup is asked or unasked and none failed, failed as soon as one
|
||||
// failed, done when every one is.
|
||||
State string `json:"state"`
|
||||
// Asked is one entry per machine and module, sorted by machine then module.
|
||||
Asked []PlanBackup `json:"asked"`
|
||||
// NoHolder are the machines that run a module with an item protected by a backup and hold no node-backup:
|
||||
// nothing is asked of them, and the plan says so.
|
||||
NoHolder []string `json:"no_holder,omitempty"`
|
||||
}
|
||||
|
||||
// PlanBackup is one backup asked of one machine's backup holder for one module.
|
||||
type PlanBackup struct {
|
||||
Machine string `json:"machine"`
|
||||
Module string `json:"module"`
|
||||
// State is pending, done or failed.
|
||||
State string `json:"state"`
|
||||
// AskedAt is when the holder was asked; nil while it is not asked yet.
|
||||
AskedAt *time.Time `json:"asked_at,omitempty"`
|
||||
// Words are the backup's own: the holder's error for a failed one, "did not answer in ten minutes" for a silent one.
|
||||
Words string `json:"words,omitempty"`
|
||||
}
|
||||
|
||||
// PlanTimes are a walk's own moments beside its modules' (novox/hq ADR 0282 decision 6).
|
||||
type PlanTimes struct {
|
||||
// WindowClosed is when its batch's merge window closed: no merge for the window's length, or its maximum.
|
||||
|
||||
Reference in New Issue
Block a user