Read the plan's backups as the controller says them: machine, module, state, words (issue 1068)
This commit is contained in:
@@ -6,10 +6,12 @@ import (
|
||||
)
|
||||
|
||||
// A failed backup before the send holds the delivery, and a release asks the backups again (novox/hq ADR 0404
|
||||
// clause 4, issue 1068). The walk's field names are provisional until the controller's contract settles them.
|
||||
// clause 4, issue 1068). The walk's backups field is the controller's contract.
|
||||
|
||||
func failedBackup() *WalkBackups {
|
||||
return &WalkBackups{Failed: []WalkBackupFailure{{Node: "ace", Module: "pg", Error: "no space left on device"}}}
|
||||
return &WalkBackups{State: "failed", Asked: []WalkBackupAsked{
|
||||
{Machine: "ace", Module: "mail", State: "done"},
|
||||
{Machine: "ace", Module: "pg", State: "failed", Words: "no space left on device"}}}
|
||||
}
|
||||
|
||||
// aDeliveringWalk is a delivery whose walk was let go and runs, nothing sent yet.
|
||||
@@ -119,7 +121,7 @@ func TestADeliveryHeldForAFailedBackupIsNotLetGoByItsWalkAlone(t *testing.T) {
|
||||
// The controller's own path starts a walk with nobody's word: the held-to-delivering row for that must not
|
||||
// undo the backup hold.
|
||||
d := &Delivery{ID: "novox/app@c0", State: Held, MergedAs: "aaaa", Walk: &WalkSeen{ID: "plan-1", State: walkBuilding, Revision: 3,
|
||||
BackupsFailed: failedBackup().Failed}}
|
||||
BackupsFailed: []WalkBackupFailure{{Machine: "ace", Module: "pg", Words: "no space left on device"}}}}
|
||||
if _, err := Apply(d, EvGo, false, Facts{}); err == nil {
|
||||
t.Fatal("a walk started on the controller's own path lifted the backup hold")
|
||||
}
|
||||
|
||||
@@ -566,8 +566,8 @@ func TestAGroupOfTwoWithGreenChecksGetsOneAskAndOnItsApproveBothMergeInOnePassOv
|
||||
// **The acceptance of a failed backup before the send, on the mesh-delivery side** (novox/hq ADR 0404 clause 4, design 47
|
||||
// milestone 4, issue 1068): a merged delivery whose walk runs is held when the walk's record, as the controller's
|
||||
// `plan-moved` carries it, names a backup that failed and nothing was sent; the hold names the machine, the module and the
|
||||
// backup's words, and nothing asks the controller to let it go. One test function, one bus. The walk's backups field names
|
||||
// are provisional until the controller's contract (issue 1065) settles them.
|
||||
// backup's words, and nothing asks the controller to let it go. One test function, one bus. The walk's backups field is the controller's
|
||||
// contract (issue 1065).
|
||||
func TestAPlanMovedCarryingAFailedBackupHoldsTheDeliveryWithTheBackupsWordsOverARealBus(t *testing.T) {
|
||||
b := newTestBus(t)
|
||||
rt := b.start(t)
|
||||
@@ -603,7 +603,7 @@ func TestAPlanMovedCarryingAFailedBackupHoldsTheDeliveryWithTheBackupsWordsOverA
|
||||
rt.until("the delivery to be delivering", 10*time.Second, func() bool { return deliveryOf().State == Delivering })
|
||||
|
||||
// A backup failed before the send: the delivery is held with the backup's words.
|
||||
walk.Revision, walk.Backups = 2, &WalkBackups{Failed: []WalkBackupFailure{{Node: "ace", Module: "pg", Error: "no space left on device"}}}
|
||||
walk.Revision, walk.Backups = 2, &WalkBackups{State: "failed", Asked: []WalkBackupAsked{{Machine: "ace", Module: "pg", State: "failed", Words: "no space left on device"}}}
|
||||
b.publish(t, "mesh.seat."+ControllerSeat+".event.plan-moved", walk)
|
||||
rt.until("the delivery to be held for the failed backup", 10*time.Second, func() bool { return deliveryOf().State == Held })
|
||||
d := deliveryOf()
|
||||
|
||||
@@ -131,7 +131,7 @@ func (c *fakeController) BackupsAgain(walk, why string) error {
|
||||
return errors.New("down")
|
||||
}
|
||||
w, known := c.walks[walk]
|
||||
if !known || w.Backups == nil || len(w.Backups.Failed) == 0 {
|
||||
if !known || w.Backups == nil || w.Backups.State != "failed" {
|
||||
return fmt.Errorf("%s holds no failed backup", walk)
|
||||
}
|
||||
w.Backups = nil
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -506,23 +505,35 @@ type Walk struct {
|
||||
Release *WalkRelease `json:"release,omitempty"`
|
||||
Delivery *WalkDelivery `json:"delivery,omitempty"`
|
||||
// Backups is what the backups the controller asks before it sends anything came to (novox/hq ADR 0404, issue
|
||||
// 1068). PROVISIONAL field name and shape until the controller's contract (issue 1065) settles them.
|
||||
// 1068); absent when the plan moves no backed-up item.
|
||||
Backups *WalkBackups `json:"backups,omitempty"`
|
||||
// Phases is the walk's time from its merge, phase by phase, as the controller works it out (novox/hq ADR
|
||||
// 0282): what a delivery's time is kept from. Absent from a controller that does not say it.
|
||||
Phases *WalkPhases `json:"phases,omitempty"`
|
||||
}
|
||||
|
||||
// WalkBackups is the backups of a walk before its send: the failed ones, each with the backup's own words.
|
||||
// WalkBackups is the backups of a walk before its send, as the controller's plan record says them: state pending,
|
||||
// done or failed (failed as soon as any entry failed), each backup asked, and the machines with no backup holder.
|
||||
type WalkBackups struct {
|
||||
Failed []WalkBackupFailure `json:"failed,omitempty"`
|
||||
State string `json:"state"`
|
||||
Asked []WalkBackupAsked `json:"asked,omitempty"`
|
||||
NoHolder []string `json:"no_holder,omitempty"`
|
||||
}
|
||||
|
||||
// WalkBackupFailure is one backup that failed or stayed silent past its bound.
|
||||
// WalkBackupAsked is one backup asked of a machine for a module: its state, and the backup's own words.
|
||||
type WalkBackupAsked struct {
|
||||
Machine string `json:"machine"`
|
||||
Module string `json:"module"`
|
||||
State string `json:"state"`
|
||||
AskedAt *time.Time `json:"asked_at,omitempty"`
|
||||
Words string `json:"words,omitempty"`
|
||||
}
|
||||
|
||||
// WalkBackupFailure is one backup that failed or stayed silent past its bound, as a delivery keeps it.
|
||||
type WalkBackupFailure struct {
|
||||
Node string `json:"node"`
|
||||
Module string `json:"module"`
|
||||
Error string `json:"error"`
|
||||
Machine string `json:"machine"`
|
||||
Module string `json:"module"`
|
||||
Words string `json:"words"`
|
||||
}
|
||||
|
||||
// WalkModule is one module of a walk.
|
||||
@@ -740,7 +751,7 @@ type WalkSeen struct {
|
||||
func (w *WalkSeen) backupWords() string {
|
||||
var parts []string
|
||||
for _, f := range w.BackupsFailed {
|
||||
parts = append(parts, fmt.Sprintf("%s on %s: %s", f.Module, f.Node, f.Error))
|
||||
parts = append(parts, fmt.Sprintf("%s on %s: %s", f.Module, f.Machine, f.Words))
|
||||
}
|
||||
return "a backup before the send failed, and nothing was sent: " + strings.Join(parts, "; ")
|
||||
}
|
||||
@@ -776,8 +787,12 @@ func (w Walk) Seen() *WalkSeen {
|
||||
}
|
||||
sort.Strings(s.Moved)
|
||||
s.MovedSaid = true
|
||||
if w.Backups != nil && len(w.Backups.Failed) > 0 {
|
||||
s.BackupsFailed = slices.Clone(w.Backups.Failed)
|
||||
if w.Backups != nil {
|
||||
for _, a := range w.Backups.Asked {
|
||||
if a.State == "failed" {
|
||||
s.BackupsFailed = append(s.BackupsFailed, WalkBackupFailure{Machine: a.Machine, Module: a.Module, Words: a.Words})
|
||||
}
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ type Controller interface {
|
||||
// Deliver lets a waiting walk start.
|
||||
Deliver(walk, why string) error
|
||||
// BackupsAgain asks the controller to take a held walk's failed backups again, and to go on when they pass
|
||||
// (novox/hq ADR 0404 clause 4). PROVISIONAL verb name until the controller's contract settles it.
|
||||
// (novox/hq ADR 0404 clause 4).
|
||||
BackupsAgain(walk, why string) error
|
||||
// Stop ends a walk, said as stopped by whom.
|
||||
Stop(walk, why, by string) error
|
||||
|
||||
@@ -90,7 +90,7 @@ func aDeliveryFor(r Row) (*Delivery, Facts) {
|
||||
d.Walk = &WalkSeen{ID: "plan-1", State: walkBuilding, Waits: true, WaitedFor: "mesh-delivery"}
|
||||
case EvBackupFailed:
|
||||
d.Walk = &WalkSeen{ID: "plan-1", State: walkBuilding, Revision: 4,
|
||||
BackupsFailed: []WalkBackupFailure{{Node: "ace", Module: "pg", Error: "no space left on device"}}}
|
||||
BackupsFailed: []WalkBackupFailure{{Machine: "ace", Module: "pg", Words: "no space left on device"}}}
|
||||
case EvFailed:
|
||||
d.Walk = &WalkSeen{ID: "plan-1", State: walkFailed}
|
||||
case EvSuperseded:
|
||||
|
||||
Reference in New Issue
Block a user