Skip to content

Commit 7810766

Browse files
committed
Validate microvm constraints on snapshot backend switch
1 parent c81ca79 commit 7810766

2 files changed

Lines changed: 102 additions & 8 deletions

File tree

lib/instances/snapshot.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@ func (m *manager) restoreSnapshot(ctx context.Context, id string, snapshotID str
273273
if err != nil {
274274
return nil, err
275275
}
276+
targetMachineType, err := m.resolveSnapshotTargetMachineType(rec.StoredMetadata, targetHypervisor)
277+
if err != nil {
278+
return nil, err
279+
}
276280

277281
target, err := m.cancelAndWaitCompressionJob(ctx, m.snapshotJobKeyForSnapshot(snapshotID))
278282
if err != nil {
@@ -303,10 +307,7 @@ func (m *manager) restoreSnapshot(ctx context.Context, id string, snapshotID str
303307
restored.ExitCode = nil
304308
restored.ExitMessage = ""
305309
restored.HypervisorType = targetHypervisor
306-
restored.MachineType, err = normalizeSnapshotMachineType(restored.MachineType, rec.StoredMetadata.HypervisorType, targetHypervisor)
307-
if err != nil {
308-
return nil, err
309-
}
310+
restored.MachineType = targetMachineType
310311

311312
starter, err := m.getVMStarter(targetHypervisor)
312313
if err != nil {
@@ -400,6 +401,10 @@ func (m *manager) forkSnapshot(ctx context.Context, snapshotID string, req ForkS
400401
if err != nil {
401402
return nil, err
402403
}
404+
targetMachineType, err := m.resolveSnapshotTargetMachineType(rec.StoredMetadata, targetHypervisor)
405+
if err != nil {
406+
return nil, err
407+
}
403408

404409
forkID := cuid2.Generate()
405410
if _, err := m.loadMetadata(forkID); err == nil {
@@ -441,10 +446,7 @@ func (m *manager) forkSnapshot(ctx context.Context, snapshotID string, req ForkS
441446
forkMeta.HypervisorPID = nil
442447
forkMeta.DataDir = dstDir
443448
forkMeta.HypervisorType = targetHypervisor
444-
forkMeta.MachineType, err = normalizeSnapshotMachineType(forkMeta.MachineType, rec.StoredMetadata.HypervisorType, targetHypervisor)
445-
if err != nil {
446-
return nil, err
447-
}
449+
forkMeta.MachineType = targetMachineType
448450
if targetHypervisor != rec.StoredMetadata.HypervisorType {
449451
hvVersion, err := starter.GetVersion(m.paths)
450452
if err != nil {
@@ -556,6 +558,30 @@ func (m *manager) resolveSnapshotTargetHypervisor(rec *snapshotRecord, requested
556558
return requested, nil
557559
}
558560

561+
func (m *manager) resolveSnapshotTargetMachineType(stored StoredMetadata, target hypervisor.Type) (hypervisor.MachineType, error) {
562+
if target == hypervisor.TypeQEMUMicroVM {
563+
var gpu *GPUConfig
564+
if stored.GPUProfile != "" {
565+
gpu = &GPUConfig{Profile: stored.GPUProfile}
566+
}
567+
if _, err := m.resolveCreateMachineType(CreateInstanceRequest{
568+
HotplugSize: stored.HotplugSize,
569+
Devices: stored.Devices,
570+
Volumes: stored.Volumes,
571+
NetworkEnabled: stored.NetworkEnabled,
572+
GPU: gpu,
573+
}, target); err != nil {
574+
return "", err
575+
}
576+
}
577+
578+
machineType, err := normalizeSnapshotMachineType(stored.MachineType, stored.HypervisorType, target)
579+
if err != nil {
580+
return "", err
581+
}
582+
return machineType, nil
583+
}
584+
559585
func resolveSnapshotTargetState(kind SnapshotKind, requested State) (State, error) {
560586
resolved, err := snapshotstore.ResolveTargetState(kind, string(requested))
561587
if err != nil {

lib/instances/snapshot_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,74 @@ func TestStandbySnapshotRejectsTargetHypervisorOverride(t *testing.T) {
8484
assert.ErrorIs(t, err, ErrInvalidRequest)
8585
}
8686

87+
func TestStoppedSnapshotRestoreRejectsIncompatibleMicroVMSwitch(t *testing.T) {
88+
t.Parallel()
89+
mgr, _ := setupTestManager(t)
90+
if _, err := mgr.getVMStarter(hypervisor.TypeQEMUMicroVM); err != nil {
91+
t.Skipf("qemu-microvm starter unavailable: %v", err)
92+
}
93+
ctx := context.Background()
94+
95+
sourceID := "snapshot-stopped-restore-microvm-src"
96+
createStoppedSnapshotSourceFixture(t, mgr, sourceID, "snapshot-stopped-restore-microvm-src", mgr.defaultHypervisor)
97+
98+
sourceMeta, err := mgr.loadMetadata(sourceID)
99+
require.NoError(t, err)
100+
sourceMeta.HotplugSize = 1
101+
require.NoError(t, mgr.saveMetadata(sourceMeta))
102+
103+
snap, err := mgr.CreateSnapshot(ctx, sourceID, CreateSnapshotRequest{
104+
Kind: SnapshotKindStopped,
105+
Name: "stopped-microvm-restore",
106+
})
107+
require.NoError(t, err)
108+
109+
_, err = mgr.RestoreSnapshot(ctx, sourceID, snap.Id, RestoreSnapshotRequest{
110+
TargetState: StateStopped,
111+
TargetHypervisor: hypervisor.TypeQEMUMicroVM,
112+
})
113+
require.Error(t, err)
114+
assert.ErrorIs(t, err, ErrInvalidRequest)
115+
assert.Contains(t, err.Error(), "does not support hotplug memory")
116+
117+
after, err := mgr.loadMetadata(sourceID)
118+
require.NoError(t, err)
119+
assert.Equal(t, mgr.defaultHypervisor, after.HypervisorType)
120+
assert.Equal(t, int64(1), after.HotplugSize)
121+
}
122+
123+
func TestStoppedSnapshotForkRejectsIncompatibleMicroVMSwitch(t *testing.T) {
124+
t.Parallel()
125+
mgr, _ := setupTestManager(t)
126+
if _, err := mgr.getVMStarter(hypervisor.TypeQEMUMicroVM); err != nil {
127+
t.Skipf("qemu-microvm starter unavailable: %v", err)
128+
}
129+
ctx := context.Background()
130+
131+
sourceID := "snapshot-stopped-fork-microvm-src"
132+
createStoppedSnapshotSourceFixture(t, mgr, sourceID, "snapshot-stopped-fork-microvm-src", mgr.defaultHypervisor)
133+
134+
sourceMeta, err := mgr.loadMetadata(sourceID)
135+
require.NoError(t, err)
136+
sourceMeta.HotplugSize = 1
137+
require.NoError(t, mgr.saveMetadata(sourceMeta))
138+
139+
snap, err := mgr.CreateSnapshot(ctx, sourceID, CreateSnapshotRequest{
140+
Kind: SnapshotKindStopped,
141+
Name: "stopped-microvm-fork",
142+
})
143+
require.NoError(t, err)
144+
145+
_, err = mgr.ForkSnapshot(ctx, snap.Id, ForkSnapshotRequest{
146+
Name: "snapshot-fork-microvm-invalid",
147+
TargetState: StateStopped,
148+
TargetHypervisor: hypervisor.TypeQEMUMicroVM,
149+
})
150+
require.Error(t, err)
151+
assert.ErrorIs(t, err, ErrInvalidRequest)
152+
assert.Contains(t, err.Error(), "does not support hotplug memory")
153+
}
154+
87155
func TestRestoreSnapshotCancelsSourceInstanceCompressionJob(t *testing.T) {
88156
t.Parallel()
89157

0 commit comments

Comments
 (0)