33import tty
44import time
55import json
6+ import re
67import select
78import termios
89import docker
@@ -657,24 +658,81 @@ def snapshot(self, id=None, action: str = None, memory_path: str = None, snapsho
657658 snapshot_file = snapshot_path if snapshot_path is not None else self ._snapshot_path
658659 self ._prepare_snapshot_rootfs_symlink (snapshot_file , rootfs_path )
659660
660- self ._api .load_snapshot .put (
661- enable_diff_snapshots = True ,
662- mem_backend = {
663- "backend_type" : "File" ,
664- "backend_path" : memory_path if memory_path is not None else self ._mem_file_path
665- },
666- snapshot_path = snapshot_file ,
667- resume_vm = True ,
668- network_overrides = [
669- {
670- "iface_id" : self ._iface_name ,
671- "host_dev_name" : self ._host_dev_name
672- }
673- ]
674- )
675- if self ._config .verbose :
676- self ._logger .debug (f"Snapshot loaded from { snapshot_file } " )
677- self ._logger .info (f"Snapshot loaded for VMM { id } " )
661+ # Try to load the snapshot
662+ try :
663+ self ._api .load_snapshot .put (
664+ enable_diff_snapshots = True ,
665+ mem_backend = {
666+ "backend_type" : "File" ,
667+ "backend_path" : memory_path if memory_path is not None else self ._mem_file_path
668+ },
669+ snapshot_path = snapshot_file ,
670+ resume_vm = True ,
671+ network_overrides = [
672+ {
673+ "iface_id" : self ._iface_name ,
674+ "host_dev_name" : self ._host_dev_name
675+ }
676+ ]
677+ )
678+ if self ._config .verbose :
679+ self ._logger .debug (f"Snapshot loaded from { snapshot_file } " )
680+ self ._logger .info (f"Snapshot loaded for VMM { id } " )
681+
682+ except Exception as load_error :
683+ # If load failed due to missing rootfs file, try to extract path from error and create symlink
684+ error_msg = str (load_error )
685+ if "No such file or directory" in error_msg and ".img" in error_msg :
686+ # Extract the expected path from error message
687+ # Error format: "... No such file or directory (os error 2) /path/to/file.img"
688+ match = re .search (r'(\S+\.img)' , error_msg )
689+ if match :
690+ expected_path = match .group (1 )
691+ if self ._config .verbose :
692+ self ._logger .info (f"Snapshot load failed: rootfs not found at { expected_path } " )
693+ self ._logger .info (f"Creating symlink from error path: { expected_path } -> { rootfs_path } " )
694+
695+ # Create symlink and retry
696+ try :
697+ expected_dir = os .path .dirname (expected_path )
698+ if not os .path .exists (expected_dir ):
699+ os .makedirs (expected_dir , mode = 0o755 , exist_ok = True )
700+
701+ # Remove existing file/symlink if needed
702+ if os .path .exists (expected_path ) or os .path .islink (expected_path ):
703+ os .remove (expected_path )
704+
705+ # Create symlink
706+ os .symlink (rootfs_path , expected_path )
707+ if self ._config .verbose :
708+ self ._logger .info (f"Created symlink: { expected_path } -> { rootfs_path } " )
709+
710+ # Retry snapshot load
711+ self ._api .load_snapshot .put (
712+ enable_diff_snapshots = True ,
713+ mem_backend = {
714+ "backend_type" : "File" ,
715+ "backend_path" : memory_path if memory_path is not None else self ._mem_file_path
716+ },
717+ snapshot_path = snapshot_file ,
718+ resume_vm = True ,
719+ network_overrides = [
720+ {
721+ "iface_id" : self ._iface_name ,
722+ "host_dev_name" : self ._host_dev_name
723+ }
724+ ]
725+ )
726+ if self ._config .verbose :
727+ self ._logger .info (f"Snapshot loaded successfully after symlink creation" )
728+ except Exception as retry_error :
729+ raise VMMError (f"Failed to load snapshot even after creating symlink: { str (retry_error )} " )
730+ else :
731+ # Could not extract path from error, re-raise original error
732+ raise
733+ else :
734+ # Different error, re-raise
735+ raise
678736 else :
679737 raise ValueError ("Invalid action. Must be 'create' or 'load'" )
680738
@@ -691,13 +749,10 @@ def _prepare_snapshot_rootfs_symlink(self, snapshot_path: str, target_rootfs_pat
691749 Args:
692750 snapshot_path (str): Path to the snapshot file
693751 target_rootfs_path (str): Actual path to the rootfs file to use
694-
695- Raises:
696- VMMError: If snapshot parsing or symlink creation fails
697752 """
698753 try :
699754 # Read and parse snapshot file to find the expected rootfs path
700- with open (snapshot_path , 'r' ) as f :
755+ with open (snapshot_path , 'r' , encoding = 'utf-8' ) as f :
701756 snapshot_data = json .load (f )
702757
703758 # Look for block devices in the snapshot
@@ -745,20 +800,24 @@ def _prepare_snapshot_rootfs_symlink(self, snapshot_path: str, target_rootfs_pat
745800 self ._logger .debug (f"Rootfs paths match, no symlink needed: { target_rootfs_path } " )
746801 else :
747802 if self ._config .verbose :
748- self ._logger .warning ("Could not find path_on_host in snapshot block device" )
803+ self ._logger .warn ("Could not find path_on_host in snapshot block device" )
749804 else :
750805 if self ._config .verbose :
751- self ._logger .warning ("No block_devices found in snapshot, skipping symlink creation" )
806+ self ._logger .warn ("No block_devices found in snapshot, skipping symlink creation" )
752807
753- except json .JSONDecodeError as e :
754- # Snapshot might be in binary format, try a different approach
808+ except (json .JSONDecodeError , UnicodeDecodeError ) as e :
809+ # Snapshot is in binary format, cannot parse to extract rootfs path
810+ # This is normal for some Firecracker versions
811+ # Silently skip symlink creation and let the load attempt proceed
755812 if self ._config .verbose :
756- self ._logger .warning (f"Could not parse snapshot as JSON: { e } . Snapshot might be in binary format. " )
757- self ._logger .warning ( "Attempting to load snapshot without symlink preparation " )
813+ self ._logger .warn (f"Snapshot is in binary format, cannot extract rootfs path for symlink creation " )
814+ self ._logger .warn ( "Proceeding without symlink - snapshot load may fail if paths don't match " )
758815 except Exception as e :
816+ # Other errors during symlink preparation - log but don't fail
817+ # Let the snapshot load attempt proceed anyway
759818 if self ._config .verbose :
760- self ._logger .warning (f"Error preparing rootfs symlink: { e } " )
761- self ._logger .warning ( "Attempting to load snapshot without symlink preparation " )
819+ self ._logger .warn (f"Error preparing rootfs symlink: { e } " )
820+ self ._logger .warn ( "Proceeding without symlink - snapshot load may fail if paths don't match " )
762821
763822 def _parse_ports (self , port_value , default_value = None ):
764823 """Parse port values from various input formats.
0 commit comments