Skip to content

fc: resume a VM restored under another name from its own disk - #1283

Merged
cdalar merged 3 commits into
mainfrom
fc-resume-moved-snapshot
Oct 29, 2026
Merged

cdalar merged 3 commits into
mainfrom
fc-resume-moved-snapshot

Conversation

@cdalar

@cdalar cdalar commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

A Firecracker snapshot records the absolute paths of its drives and vsock socket, in the VM directory it was taken in. A VM directory copied in under a new name (boxctl-vms's restore with --name) kept pointing at the old directory, so onctl resume:

  • failed when the original VM was gone. This happened on boxctl.io on 2026-10-29 for u06cadefc64-mg-box8-1:
    Block: ... No such file or directory /opt/fc-vms/vms/u06cadefc64-mg-box8/rootfs.ext4
  • loaded the original VM's live disk when the original still existed: two VMs writing to one ext4 file.

Fix

Firecracker 1.16's /snapshot/load has network_overrides and vsock_override, but no override for drives. So when the snapshot names a VM directory other than the box's own, Resume:

  1. Starts firecracker in a mount namespace of its own (unshare --mount --propagation slave), with this VM's directory bind-mounted over the recorded one. Only that process sees the mount; the host's view of the other directory is never changed.
  2. Loads the snapshot paused, with vsock_override pointing at this VM's socket.
  3. Sends PATCH /drives/rootfs (and /drives/cache if the box has one) with this VM's own files.
  4. Sends PATCH /vm {"state":"Resumed"}.

If the recorded directory didn't exist, the empty directory created to mount on is removed after the load.

Finding the recorded directory (snapshotTakenIn): bitcode stores string bytes back to back with no length prefix, so a full path can't be cut out reliably. A VM directory's name always ends at the next /, though, so matching <StateDir>/vms/<name>/ is exact. A snapshot naming more than one other directory is refused, with a pointer to onctl restart.

A box that was never moved keeps the old path: no mount, loaded with resume_vm: true, no drive PATCH.

Testing

  • make, go vet ./..., go test ./... pass.
  • New tests: a box restored under a new name (mount, paused load, vsock override, drive repointed, mount-point directory removed); the original VM's directory left unchanged when it still exists; a failed repoint stops the VMM and deletes the TAP device; a box that was never moved; snapshotTakenIn.
  • bindMountScript was dry-run with mount stubbed out.
  • Run on fc-host (Firecracker 1.16.0). Test box rt-orig was given a sleep process (its PID kept in /dev/shm) and a disk marker, then hot-paused. Two copies were made the way boxctl-vms's restore makes them (directory copied; metadata name/socketPath/rootfsPath/snapshot paths rewritten; vsockPath left pointing at rt-orig):
    1. Original still running: rt-copy1 resumed. Its firecracker had only rt-copy1/rootfs.ext4 open. The bind mount was visible only in its own mount namespace (none on the host). rt-orig's directory, its vsock socket included, was byte-for-byte the same listing. In the guest: same boot ID, sleep still alive, its own disk (it reads orig-disk; rt-orig had meanwhile written orig-changed), writes work, no I/O or ext4 errors.
    2. Second pause/resume of the copy: the new snapshot records only rt-copy1 paths. The next resume took the normal path (no mount), and state survived.
    3. Original destroyed (the production case): the installed onctl from main failed with exactly the production error (No such file or directory /opt/fc/vms/rt-orig/rootfs.ext4). This branch resumed rt-copy2 with memory and disk intact, and removed the temporary rt-orig mount directory afterwards.
  • A second commit was needed because boxctl-vms's import doesn't rewrite vsockPath/cachePath. Without it, resume would have used the original box's socket path and deleted that socket as stale, even while the original was running. Every metadata path under the snapshot's directory is now moved into the box's own directory first.
  • Unrelated, seen while testing: a non-hot onctl pause hung for more than 5 minutes in flushGuestFilesystem's SSH sync. The guest had finished its sync, but the session never returned, and that SSH command has no timeout. Tracked separately.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ba6Vc3WGrS3q5htT56ED8S

A firecracker snapshot records its drives' and vsock socket's absolute
paths, in the VM directory it was taken in. A vmDir copied in under a new
name (boxctl-vms's restore with --name) still pointed there, so resume
either failed -- "No such file or directory .../<old>/rootfs.ext4" once
the original was gone, as it did on boxctl.io on 2026-10-29 -- or, with
the original still there, loaded the original VM's live disk.

Firecracker 1.16 can override a vsock path at load but not a drive's. So
when the snapshot names another VM's directory, Resume starts firecracker
in a mount namespace of its own with this VM's directory bind-mounted over
that one, loads the snapshot paused with the vsock overridden, PATCHes
the drives to this VM's own files, then resumes. The host's view of the
other directory is never changed; one made only to mount on is removed
after the load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ba6Vc3WGrS3q5htT56ED8S
@codecov

codecov Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.58065% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.97%. Comparing base (d6f9fcc) to head (8dc2aa0).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
internal/providerfc/common.go 14.28% 12 Missing ⚠️
pkg/cloud/fc.go 89.58% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1283      +/-   ##
==========================================
+ Coverage   50.85%   50.97%   +0.11%     
==========================================
  Files          83       83              
  Lines        9888     9943      +55     
==========================================
+ Hits         5029     5068      +39     
- Misses       4859     4875      +16     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

cdalar and others added 2 commits October 29, 2026 13:09
boxctl-vms's import rewrites a copied VM's rootfs path but not its vsock
or cache disk path, so Resume would have overridden the vsock with the
original VM's socket and removed it as stale -- the live socket of an
original VM still running there. Every metadata path under the snapshot's
directory now moves to the VM's own before anything uses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ba6Vc3WGrS3q5htT56ED8S
…replace it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ba6Vc3WGrS3q5htT56ED8S
@cdalar
cdalar merged commit 1eb47f2 into main Oct 29, 2026
11 checks passed
@cdalar
cdalar deleted the fc-resume-moved-snapshot branch October 29, 2026 12:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

⌂ 换网址