agent v0.35.0: intermediary mount — guest-reboot re-propagation (load-bearing)
A guest's parent bind is non-recursive, so a guest reboot leaves enrolled drives bound on the HOST but invisible in the fresh guest ns (propagation only delivers new events). AttachDrive(vmid) now checks GuestSeesMount (/proc/<pid>/mountinfo) and force re-binds (umount+mount) to re-propagate; a 20s periodic reconcile self-heals guest reboots without an agent restart; BoundUnderParent reflects guest visibility (the controller gate's signal). Caught + fixed in the live migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,11 +69,15 @@ type GuestLister interface {
|
||||
type GuestAttacher interface {
|
||||
// AttachDrive (intermediary model) binds the drive's felhom-data under the shared parent so it
|
||||
// appears live in the guest at the returned stable path — no pct, no reboot. `where` = raw /mnt/<name>.
|
||||
AttachDrive(ctx context.Context, where string) (guestPath string, err error)
|
||||
// vmid is needed to verify GUEST visibility (a guest reboot needs a fresh re-bind to re-propagate).
|
||||
AttachDrive(ctx context.Context, vmid int, where string) (guestPath string, err error)
|
||||
// DetachDrive unmounts the drive's felhom-data from the shared parent (live, fail-closed).
|
||||
DetachDrive(ctx context.Context, where string) error
|
||||
// EnsureSharedParent makes the host stable parent shared + installs the boot-persistence unit.
|
||||
EnsureSharedParent(ctx context.Context) error
|
||||
// GuestSeesMount reports whether vmid's guest sees `path` as a mount in its own namespace (the
|
||||
// guest-usable signal — distinct from the host having the bind). Backs BoundUnderParent.
|
||||
GuestSeesMount(ctx context.Context, vmid int, path string) bool
|
||||
// AttachBind is the LEGACY per-drive `pct set -mpN` bind (pre-intermediary). Retained for the
|
||||
// transition; new attaches use AttachDrive.
|
||||
AttachBind(ctx context.Context, vmid int, mountKey, where string) error
|
||||
@@ -177,7 +181,7 @@ func (s *Server) handleDisks(w http.ResponseWriter, r *http.Request, vmid int) {
|
||||
if di.Role == string(storage.RoleUserData) {
|
||||
if gp := StablePathForRaw(t.MountPath); gp != "" {
|
||||
di.GuestPath = gp
|
||||
di.BoundUnderParent = s.boundUnderParent(gp)
|
||||
di.BoundUnderParent = s.boundUnderParent(r.Context(), vmid, gp)
|
||||
}
|
||||
}
|
||||
// Inspect the backing device for the UI's data-bearing hint (the authoritative check
|
||||
@@ -389,7 +393,7 @@ func (s *Server) handleDiskGuestAttach(w http.ResponseWriter, r *http.Request, v
|
||||
if err := s.guestAttach.EnsureSharedParent(r.Context()); err != nil {
|
||||
s.logger.Warn("local-api: guest-attach — shared parent ensure failed (continuing)", "vmid", vmid, "err", err)
|
||||
}
|
||||
stable, err := s.guestAttach.AttachDrive(r.Context(), where)
|
||||
stable, err := s.guestAttach.AttachDrive(r.Context(), vmid, where)
|
||||
if err != nil {
|
||||
s.logger.Error("local-api: guest-attach (intermediary)", "vmid", vmid, "where", where, "err", err)
|
||||
writeErr(w, http.StatusBadGateway, "guest-attach failed: "+err.Error())
|
||||
@@ -685,13 +689,18 @@ func (s *Server) handleDiskFormat(w http.ResponseWriter, r *http.Request, vmid i
|
||||
"device is system/backup-protected — format requires an operator signature ("+dec.Reason+")")
|
||||
}
|
||||
|
||||
// boundUnderParent reports whether a drive's felhom-data is currently bound at its stable guest path
|
||||
// (intermediary model). Injectable via s.boundCheck for tests; defaults to the host mount-table read.
|
||||
func (s *Server) boundUnderParent(stablePath string) bool {
|
||||
// boundUnderParent reports whether a drive's felhom-data is bound at its stable guest path AND visible
|
||||
// inside the guest (the usable-in-guest signal the controller's gate keys on — a guest reboot leaves the
|
||||
// host bind in place but invisible to the guest until re-propagated). Injectable via s.boundCheck for
|
||||
// tests; defaults to the guest-namespace mount check.
|
||||
func (s *Server) boundUnderParent(ctx context.Context, vmid int, stablePath string) bool {
|
||||
if s.boundCheck != nil {
|
||||
return s.boundCheck(stablePath)
|
||||
}
|
||||
return isHostMountpoint(stablePath)
|
||||
if s.guestAttach == nil {
|
||||
return isHostMountpoint(stablePath)
|
||||
}
|
||||
return s.guestAttach.GuestSeesMount(ctx, vmid, stablePath)
|
||||
}
|
||||
|
||||
// guestBoundPaths returns the set of guest mountpoint paths (the `mp=` of each entry in the guest's
|
||||
@@ -798,7 +807,7 @@ func (s *Server) ReassertGuestBinds(ctx context.Context) {
|
||||
s.logger.Warn("reconcile: enrolled drive not present (durable-id absent) — skipping", "vmid", vmid, "durable_id", id)
|
||||
continue
|
||||
}
|
||||
stable, err := s.guestAttach.AttachDrive(ctx, where)
|
||||
stable, err := s.guestAttach.AttachDrive(ctx, vmid, where)
|
||||
if err != nil {
|
||||
s.logger.Error("reconcile: AttachDrive failed", "vmid", vmid, "where", where, "err", err)
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user