Readings and last good nights are the owners' published packages: disk-load's diskreadings and restic's backupgood, taken by series (B5b Left 3; ADR 0634 §4; issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 14:48:10 +01:00
parent c7ae96414f
commit e0eecbf3c8
43 changed files with 565 additions and 147 deletions
+4 -4
View File
@@ -18,8 +18,8 @@ package main
import (
"encoding/json"
"fmt"
"git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/health"
"os"
"path"
"sort"
@@ -50,7 +50,7 @@ type BackupFromBus struct {
// (novox/hq ADR 0571 clause 4), and the paths that module keeps data in on that machine from disk-load's readings
// (clause 3). A machine whose backup seat nobody holds, and no readings at all, are errors: never an empty list read as
// "nothing to fear".
func BackupFrom(machine string, keys map[string][]byte, readings []health.Reading) (string, []string, error) {
func BackupFrom(machine string, keys map[string][]byte, readings []diskreadings.Reading) (string, []string, error) {
holders, err := delivery.HoldersOf(keys, BackupSeat, machine)
if err != nil {
return "", nil, err
@@ -84,7 +84,7 @@ func (m *Machine) TakeBackup(machine string) {
// TakeBackupWith is TakeBackup with the ask and the reading of the seat-holders state given, for tests. A failed ask keeps the last holder and
// paths and adds why.
func (m *Machine) TakeBackupWith(machine string, readings func(node string) ([]health.Reading, error),
func (m *Machine) TakeBackupWith(machine string, readings func(node string) ([]diskreadings.Reading, error),
holderKeys func() (map[string][]byte, error)) {
var last BackupFromBus
if text, err := m.Read(BackupFile); err == nil {
@@ -94,7 +94,7 @@ func (m *Machine) TakeBackupWith(machine string, readings func(node string) ([]h
next.Looked = m.Now().UTC().Format(time.RFC3339)
next.Error = ""
seats, err := holderKeys()
var data []health.Reading
var data []diskreadings.Reading
if err == nil {
data, err = readings(machine)
}
@@ -4,8 +4,8 @@ import (
"context"
"encoding/json"
"errors"
"git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/health"
"strings"
"testing"
"time"
@@ -101,11 +101,11 @@ func TestBackupFromTheSeatsAndTheData(t *testing.T) {
func TestAFailedAskKeepsTheLastBackupPaths(t *testing.T) {
f := newFake(t)
m := f.machine()
m.TakeBackupWith("ace", func(string) ([]health.Reading, error) {
m.TakeBackupWith("ace", func(string) ([]diskreadings.Reading, error) {
return nil, errors.New("no responders")
}, noHolders)
f.backup("restic", "/storage/restic")
m.TakeBackupWith("ace", func(string) ([]health.Reading, error) {
m.TakeBackupWith("ace", func(string) ([]diskreadings.Reading, error) {
return nil, errors.New("timeout")
}, func() (map[string][]byte, error) { return backupHolders("ace", "restic"), nil })
var b BackupFromBus
@@ -154,12 +154,12 @@ func TestAStaleOrUnknownBackupListIsSaidWithItsAge(t *testing.T) {
t.Errorf("an unknown backup list is not a warning in the health: %+v", h)
}
ok := func(string) ([]health.Reading, error) {
ok := func(string) ([]diskreadings.Reading, error) {
return readingsOfPaths("hs", map[string]string{"restic/repository": "/srv/restic"}), nil
}
m.TakeBackupWith("hs", ok, func() (map[string][]byte, error) { return backupHolders("hs", "restic"), nil })
now = now.Add(2 * time.Hour)
m.TakeBackupWith("hs", func(string) ([]health.Reading, error) { return nil, errors.New("the bus did not answer") },
m.TakeBackupWith("hs", func(string) ([]diskreadings.Reading, error) { return nil, errors.New("the bus did not answer") },
func() (map[string][]byte, error) { return backupHolders("hs", "restic"), nil })
s := m.Pass(context.Background(), nil)
said := strings.Join(append(append([]string{}, s.Said...), s.Warnings...), "\n")
@@ -181,11 +181,11 @@ func backupHolders(node, module string) map[string][]byte {
func noHolders() (map[string][]byte, error) { return nil, errors.New("the state is not read yet") }
// readingsOfPaths is disk-load's readings of one node's items, by "<module>/<item>", each at its path.
func readingsOfPaths(node string, items map[string]string) []health.Reading {
var out []health.Reading
func readingsOfPaths(node string, items map[string]string) []diskreadings.Reading {
var out []diskreadings.Reading
for k, p := range items {
module, item, _ := strings.Cut(k, "/")
out = append(out, health.Reading{V: 1, Node: node, PlacedDataItem: catalogue.PlacedDataItem{Module: module, ID: item, Path: p}})
out = append(out, diskreadings.Reading{V: 1, Node: node, PlacedDataItem: catalogue.PlacedDataItem{Module: module, ID: item, Path: p}})
}
return out
}
@@ -4,7 +4,7 @@ package main
import (
"context"
"git.novox.be/novox/mesh-sdk/go/health"
"git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings"
"strings"
"testing"
)
@@ -65,7 +65,7 @@ func TestAFailedReloadIsTriedAgainWhenNothingElseAsks(t *testing.T) {
func TestBackupFromRefusesNoReadingsAtAll(t *testing.T) {
seats := backupHolders("ace", "restic")
if _, paths, err := BackupFrom("ace", seats, []health.Reading{}); err == nil {
if _, paths, err := BackupFrom("ace", seats, []diskreadings.Reading{}); err == nil {
t.Errorf("no readings were read as the empty list %v: never \"nothing to fear\"", paths)
}
}
+5 -5
View File
@@ -20,11 +20,11 @@ import (
"syscall"
"time"
"git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/health"
"git.novox.be/novox/mesh-sdk/go/views"
)
@@ -98,13 +98,13 @@ func ShareExports(m *Machine) {
}
// readingsOf reads disk-load's readings of one machine's data items, every key `<node>.<module>.<item>`.
func readingsOf(node string) ([]health.Reading, error) {
state := stdio.State(health.ReadingsRead)
func readingsOf(node string) ([]diskreadings.Reading, error) {
state := stdio.State(diskreadings.ReadingsRead)
names, err := state.Keys()
if err != nil {
return nil, err
}
var out []health.Reading
var out []diskreadings.Reading
for _, k := range names {
if !strings.HasPrefix(k, node+".") {
continue
@@ -116,7 +116,7 @@ func readingsOf(node string) ([]health.Reading, error) {
if e == nil {
continue
}
if r, err := health.DecodeReading(e.Value); err == nil {
if r, err := diskreadings.DecodeReading(e.Value); err == nil {
out = append(out, r)
}
}
@@ -2,7 +2,7 @@ package main
import (
"encoding/json"
"git.novox.be/novox/mesh-sdk/go/health"
"git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings"
"os"
"regexp"
"sort"
@@ -11,8 +11,9 @@ import (
)
type manifest struct {
Tools []string `json:"tools"`
Claims []struct {
Packages map[string]string `json:"packages"`
Tools []string `json:"tools"`
Claims []struct {
Name string `json:"name"`
Scope string `json:"scope"`
Serves []string `json:"serves"`
@@ -44,6 +45,12 @@ func readManifest(t *testing.T) (manifest, string) {
// whether it reads or acts.
func TestTheSeatsVerbsAreAllServedAndTheHealthToolListed(t *testing.T) {
m, _ := readManifest(t)
// The readings' layouts are the owners' published packages, taken by their series (ADR 0634 §4).
for _, pk := range []string{"diskreadings"} {
if m.Packages[pk] != "0.1" {
t.Errorf("packages %v: want %s of series 0.1", m.Packages, pk)
}
}
if len(m.Claims) != 1 || m.Claims[0].Name != Seat || m.Claims[0].Scope != "node" {
t.Fatalf("claims: %+v", m.Claims)
}
@@ -141,7 +148,7 @@ func TestTheManifestCarriesGrantsTheRosterTheWantsAndTheRemovalOrder(t *testing.
t.Fatal(err)
}
// The assigner's seat-holders is asked once and followed by its events (ADR 0615), no longer read.
if len(full.Reads) != 2 || full.Reads[0] != health.ReadingsRead || full.Reads[1] != MountsState {
if len(full.Reads) != 2 || full.Reads[0] != diskreadings.ReadingsRead || full.Reads[1] != MountsState {
t.Errorf("reads: %v", full.Reads)
}
if len(full.Invokes) != 2 || full.Invokes[0] != SeatHoldersTool || full.Invokes[1] != "seat:mesh-hand-acts.hand-act" || len(full.Consumes) != 1 || full.Consumes[0] != "mesh-delivery-assigner.views.seat-holders" {
+4
View File
@@ -15,3 +15,7 @@ require (
golang.org/x/crypto v0.57.0 // indirect
golang.org/x/sys v0.48.0 // indirect
)
require git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings v0.0.0-00010101000000-000000000000
replace git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings => ../disk-load/diskreadings
+3
View File
@@ -1,6 +1,9 @@
{
"module": "nfs-server",
"version": "1",
"packages": {
"diskreadings": "0.1"
},
"upgrade": {
"policy": "record",
"why": "the folders other machines mount: a build that breaks the exports leaves every client's mount hanging or refused, and the gate on this one machine does not see the clients (hq ADR 0236, ADR 0263)"