Skip to content

Commit 02c476a

Browse files
committed
enhance: improve snapshot loading with symlink creation on error
This commit enhances the snapshot loading process in the MicroVM class by adding error handling for missing rootfs files. If the initial snapshot load fails due to a missing rootfs, the code now attempts to create a symlink based on the error message. Additionally, it updates the handling of JSON and Unicode decoding errors when parsing snapshots, allowing the process to proceed without symlink preparation if the snapshot is in binary format. This improves compatibility and robustness when loading snapshots.
1 parent 7e67e0c commit 02c476a

1 file changed

Lines changed: 89 additions & 30 deletions

File tree

firecracker/microvm.py

Lines changed: 89 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import tty
44
import time
55
import json
6+
import re
67
import select
78
import termios
89
import 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

Comments
 (0)