Skip to content

Commit cc003a7

Browse files
committed
fork test: assert mem-file at snapshot-base after firecracker restore
Firecracker enables snapshot base reuse, which renames the post-restore snapshot dir from snapshot-latest to snapshot-base. The hardlink survives the rename (same inode), so the test just needs the right path.
1 parent 006290d commit cc003a7

1 file changed

Lines changed: 23 additions & 11 deletions

File tree

lib/instances/firecracker_test.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -555,14 +555,15 @@ func TestFirecrackerSnapshotFeature(t *testing.T) {
555555

556556
// TestFirecrackerForkFromTemplate exercises the full template-driven fork
557557
// path under the state-machine design: a firecracker source goes Running →
558-
// Standby, the first fork implicitly promotes it to Template, and the fork:
558+
// Standby → Template (explicit promote), then a fork:
559559
//
560560
// (a) reaches Running,
561561
// (b) has its mem-file hardlinked to the source's snapshot mem-file
562562
// (the fan-out optimisation),
563-
// (c) bumps the template's ForkCount to 1,
563+
// (c) is counted as a live fork of the template,
564564
// (d) registers with the per-template uffd page server,
565-
// (e) on delete, decrements the ForkCount and detaches from uffd.
565+
// (e) on delete, the fork count drops back to 0 and the fork detaches
566+
// from uffd.
566567
func TestFirecrackerForkFromTemplate(t *testing.T) {
567568
t.Parallel()
568569
requireFirecrackerIntegrationPrereqs(t)
@@ -602,7 +603,11 @@ func TestFirecrackerForkFromTemplate(t *testing.T) {
602603
require.Equal(t, StateStandby, source.State)
603604
require.True(t, source.HasSnapshot)
604605

605-
// Forking from the standby source implicitly promotes it to Template.
606+
// Promote to Template explicitly — only Template sources get fan-out.
607+
source, err = mgr.PromoteToTemplate(ctx, sourceID)
608+
require.NoError(t, err)
609+
require.Equal(t, StateTemplate, source.State)
610+
606611
forked, err := mgr.ForkInstance(ctx, sourceID, ForkInstanceRequest{
607612
Name: "fc-tpl-fork",
608613
TargetState: StateRunning,
@@ -622,10 +627,15 @@ func TestFirecrackerForkFromTemplate(t *testing.T) {
622627
// (b) The fork's mem-file must share the source's inode (hardlink), not
623628
// be a copy. We can't compare paths because the link is by inode; we
624629
// compare st_ino + st_dev between the two instances' mem-files.
625-
forkMemPath := filepath.Join(p.InstanceSnapshotLatest(forkID), templateSharedMemFileName)
630+
//
631+
// Firecracker retains the post-restore snapshot dir as snapshot-base
632+
// (see restoreRetainedSnapshotBase), so after the Standby -> Running
633+
// transition the hardlink lives under snapshot-base/, not snapshot-latest/.
634+
// Hardlinks survive the rename because they bind to the inode.
635+
forkMemPath := filepath.Join(p.InstanceSnapshotBase(forkID), templateSharedMemFileName)
626636
srcMemPath := filepath.Join(p.InstanceSnapshotLatest(sourceID), templateSharedMemFileName)
627637
forkInfo, err := os.Stat(forkMemPath)
628-
require.NoError(t, err, "fork mem-file should exist at snapshot-latest/memory")
638+
require.NoError(t, err, "fork mem-file should exist at snapshot-base/memory after restore")
629639
assert.True(t, forkInfo.Mode().IsRegular(), "fork mem-file should be a regular file (hardlink), not a symlink")
630640
srcInfo, err := os.Stat(srcMemPath)
631641
require.NoError(t, err)
@@ -634,11 +644,13 @@ func TestFirecrackerForkFromTemplate(t *testing.T) {
634644
assert.Equal(t, srcSys.Ino, forkSys.Ino, "fork mem-file should share the source's inode (hardlink, not copy)")
635645
assert.Equal(t, srcSys.Dev, forkSys.Dev, "fork mem-file should be on the same filesystem as source")
636646

637-
// (c) The source instance is now a Template with ForkCount=1.
647+
// (c) The source instance is a Template with exactly one live fork.
638648
sourceMeta, err := mgr.loadMetadata(sourceID)
639649
require.NoError(t, err)
640-
assert.True(t, sourceMeta.StoredMetadata.IsTemplate, "source should be promoted to Template on first fork")
641-
assert.Equal(t, 1, sourceMeta.StoredMetadata.ForkCount, "template fork refcount should be 1 after one fork")
650+
assert.True(t, sourceMeta.StoredMetadata.IsTemplate, "source should be a Template")
651+
forks, err := mgr.countTemplateForks(sourceID)
652+
require.NoError(t, err)
653+
assert.Equal(t, 1, forks, "template fork count should be 1 after one fork")
642654

643655
// (d) The per-template uffd page server should be tracking this fork.
644656
require.NotNil(t, mgr.uffd)
@@ -648,8 +660,8 @@ func TestFirecrackerForkFromTemplate(t *testing.T) {
648660
require.NoError(t, mgr.DeleteInstance(ctx, forkID))
649661
deletedFork = true
650662

651-
sourceMetaAfter, err := mgr.loadMetadata(sourceID)
663+
forksAfter, err := mgr.countTemplateForks(sourceID)
652664
require.NoError(t, err)
653-
assert.Equal(t, 0, sourceMetaAfter.StoredMetadata.ForkCount, "template fork refcount should drop back to 0")
665+
assert.Equal(t, 0, forksAfter, "template fork count should drop back to 0")
654666
assert.False(t, mgr.uffd.hasFork(sourceID, forkID), "uffd tracker should no longer track the deleted fork")
655667
}

0 commit comments

Comments
 (0)