Files
mesh-catalog/modules/mounts/cmd/mounts/review_test.go
T
jschoubben 35ff67bda0
mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
Design 47's last milestones: the keeper, self-check, healers, hand-acts, provisioner, vault, artifact sweep, disk readings, credential forms (issues 774, 1952, 1953, 1954, 1955)
One commit with exactly the tree of the reviewed head 35b4509c0a, on main 12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
2026-10-30 02:21:51 +01:00

321 lines
14 KiB
Go

package main
// The review of 2026-10-08 (mesh-catalog #136): each finding reproduced by a test before it was fixed.
import (
"context"
"encoding/json"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/health"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// dataAnswer is mesh-controller.data's answer for this machine, as the bundle leaves it for the process.
func (f *fakeSystem) dataPaths(paths ...string) {
f.t.Helper()
if paths == nil {
paths = []string{}
}
raw, _ := json.Marshal(DataPaths{Machine: "workstation", Asked: time.Now().Format(time.RFC3339), Paths: paths})
f.write(DataFile, string(raw))
}
func shareOf(st Status, name string) ShareStatus {
for _, s := range st.Shares {
if s.Name == name {
return s
}
}
return ShareStatus{}
}
const libraryOnly = "shares=library=/srv/library\nserver=homeserver\nat=192.0.2.10\nnode=workstation\n"
// Finding 8: a setting entry that cannot be read removed the module's pair for a mount point it had named
// before, because only what parsed counted as named.
func TestAnUnreadableEntryRemovesNoMount(t *testing.T) {
f := newSystem(t)
f.dataPaths()
f.write(ExportsFile, exportsAnswer(t, library))
f.write(filepath.Join(UnitDir, "srv-library.mount"), Marker+"\n[Mount]\nWhat=192.0.2.10:/tank/media\nWhere=/srv/library\n")
f.write(filepath.Join(UnitDir, "srv-library.automount"), Marker+"\n[Automount]\nWhere=/srv/library\n")
// The operator meant library=/srv/library and wrote it without its slash.
f.write(ConfigFile, "shares=library=srv/library\nserver=homeserver\nat=192.0.2.10\nnode=workstation\n")
st := f.applier(time.Now()).Look(context.Background())
if _, err := os.Stat(filepath.Join(f.root, UnitDir, "srv-library.mount")); err != nil {
t.Fatal("an entry that cannot be read removes no pair")
}
if f.called("systemctl stop srv-library.mount") || f.called("systemctl disable --now srv-library.automount") {
t.Errorf("nothing is stopped: %q", f.calls)
}
said := strings.Join(st.Problems, " ")
if !strings.Contains(said, "srv/library") || !strings.Contains(said, "nothing is removed") {
t.Errorf("the bad entry and what is kept are said: %q", st.Problems)
}
}
// Finding 4a: a mount point already mounted by something else, or a folder with files in it, was taken.
func TestAMountPointInUseOrNotEmptyIsRefused(t *testing.T) {
f := newSystem(t)
f.dataPaths()
f.write(ConfigFile, "shares=library=/srv/library downloads=/srv/downloads\nserver=homeserver\nat=192.0.2.10\nnode=workstation\n")
f.write(ExportsFile, exportsAnswer(t, library, downloads))
// Something else is mounted at the library's mount point; the downloads' folder holds a file.
f.write("/proc/self/mountinfo", "36 1 8:2 / /srv/library rw,relatime shared:1 - ext4 /dev/sdb1 rw\n")
f.write("/srv/downloads/notes.txt", "the operator's")
st := f.applier(time.Now()).Look(context.Background())
for _, name := range []string{"library", "downloads"} {
s := shareOf(st, name)
if s.State != StateFailed {
t.Errorf("%s: %+v", name, s)
}
if f.called("systemctl enable --now srv-" + name + ".automount") {
t.Errorf("%s is not armed", name)
}
if _, err := os.Stat(filepath.Join(f.root, UnitDir, "srv-"+name+".mount")); !os.IsNotExist(err) {
t.Errorf("%s: no unit is written", name)
}
}
if s := shareOf(st, "library"); !strings.Contains(s.Why, "/dev/sdb1") {
t.Errorf("the library says what is mounted there: %q", s.Why)
}
if s := shareOf(st, "downloads"); !strings.Contains(s.Why, "not empty") {
t.Errorf("the downloads say the folder is not empty: %q", s.Why)
}
if f.read("/srv/downloads/notes.txt") != "the operator's" {
t.Error("the operator's file is untouched")
}
}
// Its own share, mounted by its own unit, is not "in use by something else".
func TestTheModulesOwnMountIsNotRefused(t *testing.T) {
f := newSystem(t)
f.dataPaths()
f.write(ConfigFile, libraryOnly)
f.write(ExportsFile, exportsAnswer(t, library))
f.write("/proc/self/mountinfo", "40 1 0:50 / /srv/library rw - autofs systemd-1 rw\n"+
"41 40 0:51 / /srv/library rw - nfs4 192.0.2.10:/tank/media rw\n")
f.write(filepath.Join(UnitDir, "srv-library.mount"), Marker+"\n")
f.write(filepath.Join(UnitDir, "srv-library.automount"), Marker+"\n")
f.reachable["192.0.2.10:2049"] = true
st := f.applier(time.Now()).Look(context.Background())
if s := shareOf(st, "library"); s.State != StateMounted {
t.Errorf("library %+v", s)
}
}
// Finding 4b: a share the machine serves itself was mounted onto itself.
func TestAShareThisMachineServesIsRefused(t *testing.T) {
f := newSystem(t)
f.dataPaths()
f.write(ConfigFile, "shares=library=/srv/library\nserver=homeserver\nat=192.0.2.10\nnode=homeserver\n")
f.write(ExportsFile, exportsAnswer(t, library))
st := f.applier(time.Now()).Look(context.Background())
s := shareOf(st, "library")
if s.State != StateFailed || !strings.Contains(s.Why, "this machine shares it") {
t.Errorf("library %+v", s)
}
if _, err := os.Stat(filepath.Join(f.root, UnitDir, "srv-library.mount")); !os.IsNotExist(err) {
t.Error("no unit is written")
}
}
// Finding 4c: removing a pair stopped its .mount even when what was mounted there was not its source.
func TestAMountThatIsNotItsOwnSourceIsNeverStopped(t *testing.T) {
f := newSystem(t)
f.dataPaths()
f.write(ConfigFile, "shares=none\nnode=workstation\n")
f.write(filepath.Join(UnitDir, "srv-old.mount"), Marker+"\n[Mount]\nWhat=192.0.2.10:/tank/old\nWhere=/srv/old\nType=nfs4\n")
f.write(filepath.Join(UnitDir, "srv-old.automount"), Marker+"\n[Automount]\nWhere=/srv/old\n")
f.write("/proc/self/mountinfo", "36 1 8:2 / /srv/old rw - ext4 /dev/sdb1 rw\n")
st := f.applier(time.Now()).Look(context.Background())
if f.called("systemctl stop srv-old.mount") || f.called("systemctl disable --now srv-old.automount") {
t.Errorf("a mount that is not its own source is not stopped: %q", f.calls)
}
if _, err := os.Stat(filepath.Join(f.root, UnitDir, "srv-old.mount")); err != nil {
t.Error("and its pair is kept")
}
if !strings.Contains(strings.Join(st.Problems, " "), "/dev/sdb1") {
t.Errorf("said: %q", st.Problems)
}
// The verb unmount refuses the same.
f.write(StatusFile, `{"shares":[{"name":"old","kind":"nfs","mountpoint":"/srv/old","what":"192.0.2.10:/tank/old","state":"mounted","mount_unit":"srv-old.mount","automount_unit":"srv-old.automount"}]}`)
f.calls = nil
_, err := callVerb(t, f.machine(), "unmount", map[string]any{"name": "old"})
if err == nil || !strings.Contains(err.Error(), "/dev/sdb1") || f.called("systemctl stop") {
t.Errorf("unmount refuses: %v %q", err, f.calls)
}
}
// Finding 4d: a mount point at, under or above data the mesh keeps on this machine was taken.
func TestAMountPointNearTheMeshsDataIsRefused(t *testing.T) {
cases := map[string]string{
"/mnt/plex": "above",
"/mnt/plex/data": "at",
"/mnt/plex/data/x": "under",
}
for point, how := range cases {
f := newSystem(t)
f.dataPaths("/mnt/plex/data", "/mnt/plex/config")
f.write(ConfigFile, "shares=library="+point+"\nserver=homeserver\nat=192.0.2.10\nnode=workstation\n")
f.write(ExportsFile, exportsAnswer(t, library))
st := f.applier(time.Now()).Look(context.Background())
s := shareOf(st, "library")
if s.State != StateFailed || !strings.Contains(s.Why, "/mnt/plex/data") || !strings.Contains(s.Why, how) {
t.Errorf("%s: %+v", point, s)
}
if f.called("systemctl enable") {
t.Errorf("%s is not armed", point)
}
}
}
// When what the mesh keeps here cannot be read, nothing new is set up; what exists stays.
func TestWithoutTheMeshsDataNothingNewIsSetUp(t *testing.T) {
f := newSystem(t)
os.Remove(filepath.Join(f.root, DataFile))
f.write(ConfigFile, libraryOnly)
f.write(ExportsFile, exportsAnswer(t, library))
st := f.applier(time.Now()).Look(context.Background())
if _, err := os.Stat(filepath.Join(f.root, UnitDir, "srv-library.mount")); !os.IsNotExist(err) {
t.Error("nothing new is written without the data list")
}
if s := shareOf(st, "library"); s.State != StateFailed || !strings.Contains(s.Why, "data") {
t.Errorf("library %+v", s)
}
// A pair that is already the module's stays and stays armed.
f.write(filepath.Join(UnitDir, "srv-library.mount"), Marker+"\n")
f.write(filepath.Join(UnitDir, "srv-library.automount"), Marker+"\n")
f.applier(time.Now()).Look(context.Background())
if _, err := os.Stat(filepath.Join(f.root, UnitDir, "srv-library.mount")); err != nil {
t.Error("an existing pair stays")
}
}
// The bundle asks the controller for this machine's data and leaves the paths for the process.
func TestTheBundleLeavesTheMeshsDataForTheProcess(t *testing.T) {
f := newSystem(t)
f.write(ConfigFile, libraryOnly)
rs := []health.Reading{{V: 1, Node: "workstation", PlacedDataItem: catalogue.PlacedDataItem{Module: "plex", ID: "data", Path: "/mnt/plex/data"}}}
paths, err := PathsFrom(rs)
if err != nil || len(paths) != 1 || paths[0] != "/mnt/plex/data" {
t.Fatalf("paths %q %v", paths, err)
}
if _, err := PathsFrom(nil); err == nil {
t.Error("no reading is an error, not an empty list")
}
}
// Finding 5: the server exports a share only to the nodes that want it, so the bundle says what this node
// wants of which server, also before the process has looked.
func TestTheBundleSaysWhatThisNodeWants(t *testing.T) {
f := newSystem(t)
f.write(ConfigFile, "shares=library=/srv/library downloads=/srv/downloads:ro\nserver=homeserver\nat=192.0.2.10\nnode=workstation\n")
var put []any
last := ""
ask := func(string) (json.RawMessage, error) { return json.RawMessage(`{"shares":[]}`), nil }
f.machine().Share("workstation", ask, func(_ string, v any) error { put = append(put, v); return nil }, &last, time.Now())
if len(put) != 1 {
t.Fatalf("the wants are put before the process has looked: %d", len(put))
}
raw, _ := json.Marshal(put[0])
var got struct {
Wants []Want `json:"wants"`
}
json.Unmarshal(raw, &got)
want := []Want{{Server: "homeserver", Share: "downloads", Access: "ro"}, {Server: "homeserver", Share: "library", Access: "rw"}}
if len(got.Wants) != 2 || got.Wants[0] != want[0] || got.Wants[1] != want[1] {
t.Errorf("wants %s", raw)
}
}
// Finding 9: the seat passes confirm as the string "true".
func TestConfirmIsTrueAsABooleanOrAString(t *testing.T) {
for _, v := range []any{true, "true"} {
if !flag(map[string]any{"confirm": v}, "confirm") {
t.Errorf("%#v is a confirmation", v)
}
}
for _, v := range []any{false, "false", "yes", "1", nil} {
if flag(map[string]any{"confirm": v}, "confirm") {
t.Errorf("%#v is not a confirmation", v)
}
}
}
// Low: a password hidden in user=name%pass, and one holding a comma written as two (",,").
func TestEveryWayAPasswordIsWrittenIsRedacted(t *testing.T) {
for _, opts := range []string{
"user=bob%" + testPassword + ",vers=3.0",
"username=bob%" + testPassword,
"password=" + testPassword + ",,tail,vers=3.0",
"password=,,," + testPassword,
"pass=" + testPassword,
} {
if got := RedactOptions(opts); strings.Contains(got, testPassword) || strings.Contains(got, "tail") {
t.Errorf("RedactOptions(%q) = %q", opts, got)
}
line := "//192.0.2.76/Share /mnt/games cifs " + opts + " 0 0"
if got := RedactLine(line); strings.Contains(got, testPassword) || strings.Contains(got, "tail") {
t.Errorf("RedactLine(%q) = %q", line, got)
}
}
if got := RedactOptions("user=bob,vers=3.0"); got != "user=bob,vers=3.0" {
t.Errorf("a user name alone is kept: %q", got)
}
}
func callVerb(t *testing.T, m *Machine, name string, args map[string]any) (any, error) {
t.Helper()
for _, tool := range Verbs(m) {
if tool.Name == Seat+"."+name {
return tool.Run(args)
}
}
t.Fatalf("no verb %s", name)
return nil, nil
}
// The bundle asks the controller, leaves the paths, and keeps the last ones when an ask fails.
func TestTheBundleKeepsTheLastDataWhenAnAskFails(t *testing.T) {
f := newSystem(t)
m := f.machine()
ok := func(node string) ([]health.Reading, error) {
return []health.Reading{{V: 1, Node: node, PlacedDataItem: catalogue.PlacedDataItem{Module: "plex", ID: "data", Path: "/mnt/plex/data"}}}, nil
}
if said := m.LeaveData("workstation", ok, time.Now()); len(said) != 0 {
t.Fatalf("said %q", said)
}
failing := func(string) ([]health.Reading, error) { return nil, os.ErrDeadlineExceeded }
said := m.LeaveData("workstation", failing, time.Now())
d, err := readJSON[DataPaths](m, DataFile)
if err != nil || len(d.Paths) != 1 || d.Paths[0] != "/mnt/plex/data" || d.Error == "" || len(said) == 0 {
t.Errorf("a failed read keeps the last paths and says why: %+v %v %q", d, err, said)
}
}
// The server decides each node's access (finding 5): a share it exports read-write may reach this node
// read-only, and a share it exports to other nodes only does not reach this one at all. The mount takes
// the access the server gave this node, never the share's own.
func TestTheServersAccessForThisNodeIsTheMountsAccess(t *testing.T) {
library := ServerShare{Name: "library", Path: "/srv/library", Access: "rw", Live: true,
To: []ShareClient{{Node: "ws", Address: "10.0.0.3", Access: "rw"}, {Node: "laptop", Address: "10.0.0.4", Access: "ro"}}}
got, ok := library.ForNode("laptop")
if !ok || got.Access != "ro" {
t.Errorf("the laptop, granted read-only, gets %q (exported to it: %v)", got.Access, ok)
}
if p := NFSPair(Wanted{Name: "library", Mountpoint: "/srv/library"}, "192.0.2.10", got); !strings.Contains(p.Mount, "Options=ro,") {
t.Errorf("a read-only grant mounted read-write:\n%s", p.Mount)
}
if got, ok := library.ForNode("ws"); !ok || got.Access != "rw" {
t.Errorf("the workstation, granted read-write, gets %q (%v)", got.Access, ok)
}
if _, ok := library.ForNode("control"); ok {
t.Errorf("a node the server does not export to is treated as exported to")
}
}