@@ -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.
283326const vmConfigFile = "qemu-config.json"
284327
0 commit comments