Skip to content

Commit f536cd2

Browse files
committed
Fix restore on qemu, test passing
1 parent ccac7d7 commit f536cd2

File tree

2 files changed

+48
-4
lines changed

2 files changed

+48
-4
lines changed

lib/hypervisor/qemu/process.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"syscall"
1515
"time"
1616

17+
"github.com/digitalocean/go-qemu/qmp/raw"
1718
"github.com/onkernel/hypeman/lib/hypervisor"
1819
"github.com/onkernel/hypeman/lib/paths"
1920
"gvisor.dev/gvisor/pkg/cleanup"
@@ -216,8 +217,9 @@ func (s *Starter) RestoreVM(ctx context.Context, p *paths.Paths, version string,
216217
args = append(args, BuildArgs(config)...)
217218

218219
// Add incoming migration flag to restore from snapshot
219-
// The snapshot file is named "memory" in the snapshot directory
220-
incomingURI := "file://" + filepath.Join(snapshotPath, "memory")
220+
// The "file:" protocol is deprecated in QEMU 7.2+, use "exec:cat < path" instead
221+
memoryFile := filepath.Join(snapshotPath, "memory")
222+
incomingURI := "exec:cat < " + memoryFile
221223
args = append(args, "-incoming", incomingURI)
222224

223225
// Create command
@@ -274,11 +276,52 @@ func (s *Starter) RestoreVM(ctx context.Context, p *paths.Paths, version string,
274276
return 0, nil, fmt.Errorf("create client: %w", err)
275277
}
276278

279+
// Wait for incoming migration to complete
280+
// QEMU loads the migration data from the exec subprocess
281+
// After loading, VM is in paused state and ready for 'cont'
282+
if err := waitForMigrationComplete(hv.client, 30*time.Second); err != nil {
283+
return 0, nil, fmt.Errorf("wait for migration: %w", err)
284+
}
285+
277286
// Success - release cleanup to prevent killing the process
278287
cu.Release()
279288
return pid, hv, nil
280289
}
281290

291+
// waitForMigrationComplete waits for incoming migration to finish loading
292+
func waitForMigrationComplete(client *Client, timeout time.Duration) error {
293+
deadline := time.Now().Add(timeout)
294+
for time.Now().Before(deadline) {
295+
info, err := client.QueryMigration()
296+
if err != nil {
297+
// Ignore errors during migration
298+
time.Sleep(100 * time.Millisecond)
299+
continue
300+
}
301+
302+
if info.Status == nil {
303+
// No migration status yet, might be loading
304+
time.Sleep(100 * time.Millisecond)
305+
continue
306+
}
307+
308+
switch *info.Status {
309+
case raw.MigrationStatusCompleted:
310+
return nil
311+
case raw.MigrationStatusFailed:
312+
return fmt.Errorf("migration failed")
313+
case raw.MigrationStatusCancelled:
314+
return fmt.Errorf("migration cancelled")
315+
case raw.MigrationStatusNone:
316+
// No active migration - incoming may have completed
317+
return nil
318+
}
319+
320+
time.Sleep(100 * time.Millisecond)
321+
}
322+
return fmt.Errorf("migration timeout")
323+
}
324+
282325
// vmConfigFile is the name of the file where VM config is saved for restore.
283326
const vmConfigFile = "qemu-config.json"
284327

lib/hypervisor/qemu/qemu.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ func (q *QEMU) Resume(ctx context.Context) error {
126126
// The VM config is copied to destPath for restore (QEMU requires exact arg match).
127127
func (q *QEMU) Snapshot(ctx context.Context, destPath string) error {
128128
// QEMU uses migrate to file for snapshots
129-
// The file URI must be absolute path
130-
uri := "file://" + destPath + "/memory"
129+
// The "file:" protocol is deprecated in QEMU 7.2+, use "exec:cat > path" instead
130+
memoryFile := destPath + "/memory"
131+
uri := "exec:cat > " + memoryFile
131132
if err := q.client.Migrate(uri); err != nil {
132133
Remove(q.socketPath)
133134
return fmt.Errorf("migrate: %w", err)

0 commit comments

Comments
 (0)