agent v0.34.0: intermediary mount model — shared parent + host-side attach/detach + reconcile
Replaces the per-drive 'pct set -mpN' bind with ONE permanent parent bind /mnt/felhom-drives plus host-side felhom-data swaps underneath it (propagates into the running guest live, no pct, no reboot; C1-immune; confined; fail-closed when absent). EnsureSharedParent installs a boot unit ordered Before=pve-guests. ReassertGuestBinds is now a pure host-side reconcile. /disks reports GuestPath + BoundUnderParent for the controller repoint+gate. Non-hollow tests + companions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-agent/internal/hub"
|
||||
"gitea.dooplex.hu/admin/felhom-agent/internal/storage"
|
||||
)
|
||||
|
||||
func TestStablePathForRaw_DriveName(t *testing.T) {
|
||||
cases := []struct {
|
||||
where, name, stable string
|
||||
}{
|
||||
{"/mnt/felhom-usb", "felhom-usb", "/mnt/felhom-drives/felhom-usb"},
|
||||
{"/mnt/felhom-flash", "felhom-flash", "/mnt/felhom-drives/felhom-flash"},
|
||||
{"/mnt/felhom-drives/x", "", ""}, // nested (slash in remainder) → not a /mnt/<name> raw mount
|
||||
{"/var/lib/vz", "", ""}, // not under /mnt
|
||||
{"/mnt/", "", ""}, // empty name
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := DriveNameFromRaw(c.where); got != c.name {
|
||||
t.Errorf("DriveNameFromRaw(%q) = %q, want %q", c.where, got, c.name)
|
||||
}
|
||||
if got := StablePathForRaw(c.where); got != c.stable {
|
||||
t.Errorf("StablePathForRaw(%q) = %q, want %q", c.where, got, c.stable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDisks_GuestPathAndBoundUnderParent asserts the intermediary reporting the controller relies on: a
|
||||
// user-data drive gets a STABLE guest path (/mnt/felhom-drives/<name>) and BoundUnderParent reflects the
|
||||
// host mount check; a SYSTEM mount gets neither (it never crosses into the guest).
|
||||
//
|
||||
// COMPANION GUARD: a pre-fix impl that left GuestPath empty (no repoint signal) or that reported
|
||||
// BoundUnderParent ignoring the real mount state would fail the assertions below.
|
||||
func TestDisks_GuestPathAndBoundUnderParent(t *testing.T) {
|
||||
d := &fakeDiskOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true, FSType: "ext4"}}
|
||||
sv := fakeStorage{targets: []hub.StorageTarget{
|
||||
{Name: "usb", Type: hub.StorageTypeUSB, BackingDevice: "/dev/sdb1", MountPath: "/mnt/felhom-usb"},
|
||||
{Name: "flash", Type: hub.StorageTypeUSB, BackingDevice: "/dev/sdc1", MountPath: "/mnt/felhom-flash"},
|
||||
{Name: "system", Type: "local", MountPath: "/var/lib/vz"}, // system role → no guest path
|
||||
}}
|
||||
fg := &fakeGuestsCfg{}
|
||||
srv, err := NewServer(Options{
|
||||
ListenAddr: "127.0.0.1:0", Guests: fg, Backups: &fakeBackups{}, Store: &fakeStore{},
|
||||
Storage: sv, Tokens: staticTokens{"A": 8200},
|
||||
Disks: d, DiskGate: &fakeGate{}, HostReader: sysOnSDA(),
|
||||
Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
srv.baseCtx = context.Background()
|
||||
// felhom-usb is bound under the parent; felhom-flash is not.
|
||||
srv.boundCheck = func(p string) bool { return p == "/mnt/felhom-drives/felhom-usb" }
|
||||
|
||||
disks := decodeDisks(t, do(t, srv.Handler(), "GET", "/disks", "A", "").Body.Bytes())
|
||||
byMount := map[string]DiskInfo{}
|
||||
for _, di := range disks {
|
||||
byMount[di.MountPath] = di
|
||||
}
|
||||
|
||||
if gp := byMount["/mnt/felhom-usb"].GuestPath; gp != "/mnt/felhom-drives/felhom-usb" {
|
||||
t.Errorf("usb GuestPath = %q, want /mnt/felhom-drives/felhom-usb", gp)
|
||||
}
|
||||
if !byMount["/mnt/felhom-usb"].BoundUnderParent {
|
||||
t.Errorf("usb should report BoundUnderParent=true (bound under the parent)")
|
||||
}
|
||||
if byMount["/mnt/felhom-flash"].BoundUnderParent {
|
||||
t.Errorf("flash should report BoundUnderParent=false (not bound)")
|
||||
}
|
||||
if gp := byMount["/mnt/felhom-flash"].GuestPath; gp != "/mnt/felhom-drives/felhom-flash" {
|
||||
t.Errorf("flash GuestPath = %q, want /mnt/felhom-drives/felhom-flash", gp)
|
||||
}
|
||||
// A SYSTEM mount must never get a guest path (it does not cross into the guest).
|
||||
if gp := byMount["/var/lib/vz"].GuestPath; gp != "" {
|
||||
t.Errorf("system mount GuestPath = %q, want empty (never crosses into the guest)", gp)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user