From 08e000bb56aa6e5b68ed457564cb14d484675213 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 17 Oct 2026 23:04:52 +0200 Subject: [PATCH] Read the backup holders of every machine, and the notes fixture's data at a resource (issues 1065, 1066) --- .../backups_before_send_integration_test.go | 3 +- cmd/mesh-controller/plan_backups.go | 33 +++++++++++++++---- cmd/mesh-controller/plan_backups_test.go | 15 +++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/cmd/mesh-controller/backups_before_send_integration_test.go b/cmd/mesh-controller/backups_before_send_integration_test.go index b8342eb4..58f89ed0 100644 --- a/cmd/mesh-controller/backups_before_send_integration_test.go +++ b/cmd/mesh-controller/backups_before_send_integration_test.go @@ -98,7 +98,8 @@ func TestADeliveryThatMovesABackedUpModuleWaitsForItsBackupAndAFailedOneHoldsItA register(t, open, catalogue.Manifest{Module: "keeper", Version: "1", Claims: []catalogue.Claim{{Name: catalogue.BackupSeat, Scope: catalogue.ScopeNode, Serves: []string{"backed-up", "now", "restore"}}}}) notes, err := catalogue.ParseManifest([]byte(`{"module":"notes","version":"1","data":{"own":[` + - `{"id":"files","path":"/srv/notes","class":"valuable","why":"people's notes"}]}}`)) + `{"id":"files","path":"${dir:files}","class":"valuable","why":"people's notes"}]},` + + `"resources":[{"id":"files","type":"directory"}]}`)) if err != nil { t.Fatal(err) } diff --git a/cmd/mesh-controller/plan_backups.go b/cmd/mesh-controller/plan_backups.go index a38cd0b9..f741e126 100644 --- a/cmd/mesh-controller/plan_backups.go +++ b/cmd/mesh-controller/plan_backups.go @@ -115,17 +115,38 @@ func backupFactsFrom(entries []inventory.Entry, held []catalogue.Held) backupFac return f } -// loadBackupFacts reads the facts from the inventory. +// loadBackupFacts reads the facts from the inventory. The holders are read as `seats` reads them, from the world +// every machine's composition makes (theRestOfTheMesh): the seat_holding table keeps one holder of a node seat, and +// node-backup is held on every machine. func loadBackupFacts(ctx context.Context, inv *inventory.Inventory) (backupFacts, error) { - entries, err := inv.Catalogued(ctx) + return loadBackupFactsFrom(inv.Catalogued, func() ([]catalogue.Held, error) { + shelf, err := inv.Catalogue(ctx) + if err != nil { + return nil, err + } + world, err := theRestOfTheMesh(ctx, inv, shelf, "") + if err != nil { + return nil, err + } + return world.Held, nil + }, ctx) +} + +// loadBackupFactsFrom is loadBackupFacts given where the entries and the holdings come from. A source that fails +// fails the step, to be tried again: a failure never reads as a machine with no holder, which would send without a +// backup. +func loadBackupFactsFrom(entries func(context.Context) ([]inventory.Entry, error), + held func() ([]catalogue.Held, error), ctx context.Context) (backupFacts, error) { + es, err := entries(ctx) if err != nil { - return backupFacts{}, err + return backupFacts{}, fmt.Errorf("the backups of the plan could not be worked out: %w", err) } - held, err := inv.Holdings(ctx) + h, err := held() if err != nil { - return backupFacts{}, err + return backupFacts{}, fmt.Errorf("the backups of the plan could not be worked out: who holds %s: %w", + catalogue.BackupSeat, err) } - return backupFactsFrom(entries, held), nil + return backupFactsFrom(es, h), nil } // stepBackups takes one step of a plan's backups and says whether the plan may go on to ask its first tier. It diff --git a/cmd/mesh-controller/plan_backups_test.go b/cmd/mesh-controller/plan_backups_test.go index 804dde4e..61af531b 100644 --- a/cmd/mesh-controller/plan_backups_test.go +++ b/cmd/mesh-controller/plan_backups_test.go @@ -222,3 +222,18 @@ func TestAPlanThatMovesNoBackedUpItemAsksNothing(t *testing.T) { t.Fatalf("proceed=%v err=%v record=%+v asked=%v", proceed, err, p.Backups, seat.asked) } } + +// The holders of a node seat are every machine's, and a source that fails is no machine without a holder. +func TestTheBackupHoldersAreEveryMachinesAndAFailedReadIsNotNoHolder(t *testing.T) { + entries := func(context.Context) ([]inventory.Entry, error) { + return []inventory.Entry{aModuleWith(t, "mail", []string{"ace", "g14"}, anItem("a", ""))}, nil + } + f, err := loadBackupFactsFrom(entries, func() ([]catalogue.Held, error) { return holders("ace", "g14"), nil }, t.Context()) + if err != nil || !f.holder["ace"] || !f.holder["g14"] { + t.Fatalf("%+v %v", f, err) + } + if _, err := loadBackupFactsFrom(entries, func() ([]catalogue.Held, error) { return nil, fmt.Errorf("store down") }, + t.Context()); err == nil || !strings.Contains(err.Error(), "store down") { + t.Fatalf("a failed read of the holders must fail the step: %v", err) + } +}