Files
mesh-catalog/modules/zfs/cmd/zfs-tools/follow_wake_test.go
T
2026-11-13 00:49:01 +01:00

94 lines
3.5 KiB
Go

package main
import (
"context"
"encoding/json"
"strings"
"testing"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
"git.novox.be/novox/mesh-sdk/go/views"
)
type findingWriter struct {
st memStates
puts []string
dels []string
}
func (w *findingWriter) Put(f views.Finding) error {
key := meshconditionsclient.Key(meshconditionsclient.ScopeModule, f.ID, f.Kind)
w.puts = append(w.puts, key)
return w.st.Put(StateFindings, key, map[string]string{"summary": f.Summary})
}
func (w *findingWriter) Delete(f views.Finding) error {
key := meshconditionsclient.Key(meshconditionsclient.ScopeModule, f.ID, f.Kind)
w.dels = append(w.dels, key)
return w.st.Delete(StateFindings, key)
}
// §18, §27, A-16: the apply is started once at the follower's start and on scrub_finish, resilver_finish and a
// declared device's udev add, through the service manager; a device not ONLINE is a finding, written once,
// deleted when ONLINE again or gone.
func TestTheFollowerStartsTheApplyOnTheEventsThatEndAWaitAndKeepsTheFindings(t *testing.T) {
for class, want := range map[string]bool{"sysevent.fs.zfs.scrub_finish": true, "sysevent.fs.zfs.resilver_finish": true,
"sysevent.fs.zfs.scrub_start": false, "ereport.fs.zfs.io": false} {
if EndsAWait(class) != want {
t.Errorf("%s: %v", class, !want)
}
}
f := newFake(t)
f.uid = 0
f.files[ConfigFile] = `{"pool":"tank","cache-devices":["c1"]}`
f.files[MemInfoFile] = "MemTotal: 16384000 kB\n"
f.links["/dev/disk/by-id/c1"] = "/dev/nvme1n1"
m := f.machine()
var key string
var body any
m.Node = "ace"
m.Call = func(k string, b any) (json.RawMessage, error) { key, body = k, b; return nil, nil }
udev := map[string]bool{
"UDEV [12.3] add /devices/pci0000:00/nvme/nvme1/nvme1n1 (block)": true,
"UDEV [12.3] add /devices/pci0000:00/nvme/nvme1/nvme1n1/nvme1n1p1 (block)": true,
"UDEV [12.3] add /devices/pci0000:00/nvme/nvme2/nvme2n1 (block)": false,
"UDEV [12.3] remove /devices/pci0000:00/nvme/nvme1/nvme1n1 (block)": false,
"KERNEL[12.3] add /devices/pci0000:00/nvme/nvme1/nvme1n1 (block)": false,
}
for line, want := range udev {
if m.DeclaredAdded(line) != want {
t.Errorf("%q: %v", line, !want)
}
}
if err := m.FollowUdev(strings.NewReader("UDEV [1] add /devices/x/nvme2n1 (block)\nUDEV [2] add /devices/x/nvme1n1 (block)\n")); err != nil {
t.Fatal(err)
}
args, _ := body.(map[string]any)
if key != "seat:node-service-manager.start@ace" || args["unit"] != ApplyUnit || args["scope"] != "system" {
t.Errorf("called %s with %v", key, body)
}
// The findings: a degraded pool, then healthy again.
st := memStates{}
w := &findingWriter{st: st}
m.States, m.Findings = st, w
f.on(line("zpool", StatusPathsArgv()), fixture(t, "zpool-status-degraded.txt"))
if err := m.SyncFindings(context.Background()); err != nil {
t.Fatal(err)
}
for _, k := range []string{"pool_tank_pool", "pool_tank_raidz2-0", "pool_tank_ata-WDC_WD120EFBX-68B0EN0_D7JA0002"} {
if _, ok := st["findings/module.zfs."+k]; !ok {
t.Errorf("no finding %s in %v", k, st)
}
}
if len(w.puts) != 4 {
t.Errorf("puts %v", w.puts)
}
if err := m.SyncFindings(context.Background()); err != nil || len(w.puts) != 4 {
t.Errorf("an unchanged finding was written again: %v %v", err, w.puts)
}
f.on(line("zpool", StatusPathsArgv()), fixture(t, "zpool-status.txt"))
if err := m.SyncFindings(context.Background()); err != nil || len(w.dels) != 4 || len(st) != 0 {
t.Errorf("deleted %v, left %v, %v", w.dels, st, err)
}
}