v0.84.0: ReassertNetworkMounts — NAS automount survives guest reboots (RCA fix 1)
Storage §8 decision table (stop + enable --now on idle triggers; active mounts untouched), daemon leg at startup with per-running-guest visibility verify, guest-hook post-start leg (root, direct systemctl, non-fatal). Red-proofs: always-rearm table FAIL; unwired hook FAIL. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-agent/internal/storage"
|
||||
)
|
||||
|
||||
// networkReasserter is the optional concrete capability of the netStorage surface (satisfied by
|
||||
// *storage.SudoHostOps). Type-asserted like main.go's ReassertEnrolledMounts so the lean
|
||||
// NetworkStorageOps interface (and its test fakes) stay unchanged.
|
||||
type networkReasserter interface {
|
||||
ReassertNetworkAutomounts(ctx context.Context) []storage.NetReassertResult
|
||||
}
|
||||
|
||||
// ReassertNetworkMounts is the NAS counterpart of ReassertGuestBinds (RCA
|
||||
// AUDIT-nas-cwa-rca-2026-07-11 fix 1): re-arm every idle network-share automount trigger host-side
|
||||
// (a fresh trigger-mount event propagates live into running guests' slave binds), then best-effort
|
||||
// verify each running guest actually sees each share's path. Runs at agent startup (after the
|
||||
// guest-bind reconcile — covers guests that autostarted before the agent) and from the guest-hook
|
||||
// post-start phase (its own process; this method is the daemon path). NEVER call this from a
|
||||
// periodic health path — an idle trigger is healthy and must not be churned.
|
||||
func (s *Server) ReassertNetworkMounts(ctx context.Context) {
|
||||
if s.netStorage == nil {
|
||||
return
|
||||
}
|
||||
r, ok := s.netStorage.(networkReasserter)
|
||||
if !ok {
|
||||
s.logger.Debug("netreassert: storage surface has no reassert capability — skipping")
|
||||
return
|
||||
}
|
||||
results := r.ReassertNetworkAutomounts(ctx)
|
||||
if len(results) == 0 {
|
||||
return
|
||||
}
|
||||
// Best-effort guest-visibility verify (the RCA's masking lesson: host-side health said nothing
|
||||
// about what the guests see). Only running guests; a failed verify is a WARN, never an error —
|
||||
// the next guest start re-runs the hook path anyway.
|
||||
if s.guestBinds == nil || s.guestAttach == nil {
|
||||
return
|
||||
}
|
||||
for vmid := range s.guestBinds.Guests() {
|
||||
if s.guestAttach.GuestBootID(ctx, vmid) == "" {
|
||||
s.logger.Debug("netreassert: guest not running — visibility verify skipped", "vmid", vmid)
|
||||
continue
|
||||
}
|
||||
for _, res := range results {
|
||||
if res.Action == storage.NetReassertSkipNone || res.Err != nil {
|
||||
continue // nothing expected in the guest for these rows
|
||||
}
|
||||
if s.guestAttach.GuestSeesMount(ctx, vmid, res.Where) {
|
||||
s.logger.Debug("netreassert: guest sees network share", "vmid", vmid, "name", res.Name, "where", res.Where)
|
||||
} else {
|
||||
s.logger.Warn("netreassert: guest does NOT see network share after reassert",
|
||||
"vmid", vmid, "name", res.Name, "where", res.Where, "action", res.Action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user