Skip to content

Commit 6139829

Browse files
committed
support snapshot
support snapshot
1 parent c3c67e6 commit 6139829

File tree

2 files changed

+49
-62
lines changed

2 files changed

+49
-62
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ All examples below use `python anyvm.py ...`. You can also run `python anyvm.py
168168
- `--qcow2 <path>`: Use a local qcow2 image (skip downloading).
169169
- Example: `python anyvm.py --os freebsd --qcow2 .\\output\\freebsd\\freebsd-14.3.qcow2`
170170

171+
- `--snapshot`: Enable QEMU snapshot mode. Changes made to the disk are not saved.
172+
- Works with `--cache-dir` to run directly from the cache without copying to the data directory.
173+
- Example: `python anyvm.py --os freebsd --snapshot`
174+
171175
### Networking (user-mode networking / slirp)
172176

173177
- `--ssh-port <port>` / `--sshport <port>`: Host port forwarded to guest SSH (`:22`). If omitted, anyvm auto-picks a free port.

anyvm.py

Lines changed: 45 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,7 @@ def print_usage():
11741174
--detach, -d Run QEMU in background.
11751175
--console, -c Run QEMU in foreground (console mode).
11761176
--builder <ver> Specify a specific vmactions builder version tag.
1177+
--snapshot Enable QEMU snapshot mode (changes are not saved).
11771178
-- Send all following args to the final ssh command (executes inside the VM).
11781179
--help, -h Show this help message.
11791180
@@ -1969,7 +1970,8 @@ def main():
19691970
'enable_ipv6': False,
19701971
'debug': False,
19711972
'qcow2': "",
1972-
'cachedir': ""
1973+
'cachedir': "",
1974+
'snapshot': False
19731975
}
19741976

19751977
ssh_passthrough = []
@@ -2072,6 +2074,8 @@ def main():
20722074
elif arg == "--cache-dir":
20732075
config['cachedir'] = os.path.abspath(args[i+1])
20742076
i += 1
2077+
elif arg == "--snapshot":
2078+
config['snapshot'] = True
20752079
i += 1
20762080

20772081
if config['debug']:
@@ -2355,73 +2359,36 @@ def find_image_link(releases, target_zst, target_xz):
23552359
qcow_name += ".qcow2"
23562360

23572361
# Download and Extract
2358-
if not os.path.exists(qcow_name):
2359-
if config.get('cachedir'):
2360-
# New caching strategy: cache qcow2 instead of zst
2361-
rel_path = os.path.relpath(output_dir, working_dir)
2362-
cache_output_dir = os.path.join(config['cachedir'], rel_path)
2363-
if not os.path.exists(cache_output_dir):
2364-
debuglog(config['debug'], "Creating cache directory: {}".format(cache_output_dir))
2365-
os.makedirs(cache_output_dir)
2366-
2367-
# Check for cached qcow2 file
2368-
cached_qcow2 = os.path.join(cache_output_dir, os.path.basename(qcow_name))
2369-
2370-
if os.path.exists(cached_qcow2):
2371-
# Cache hit: copy qcow2 from cache to data-dir
2372-
debuglog(config['debug'], "Found cached qcow2: {}".format(cached_qcow2))
2373-
log("Copying cached image: {} -> {}".format(cached_qcow2, qcow_name))
2374-
start_time = time.time()
2375-
shutil.copy2(cached_qcow2, qcow_name)
2376-
duration = time.time() - start_time
2377-
debuglog(config['debug'], "Copying from cache took {:.2f} seconds".format(duration))
2378-
else:
2379-
# Cache miss: download zst to data-dir, extract, copy qcow2 to cache, delete zst
2380-
debuglog(config['debug'], "qcow2 not found in cache, downloading zst to data-dir")
2381-
2382-
if not os.path.exists(ova_file):
2383-
if download_file(zst_link, ova_file, config['debug']):
2384-
download_optional_parts(zst_link, ova_file, debug=config['debug'])
2385-
2386-
if not os.path.exists(ova_file):
2387-
fatal("Failed to download image: " + ova_file)
2388-
2389-
# Extract zst/xz to qcow2
2390-
log("Extracting " + ova_file)
2391-
extract_start_time = time.time()
2392-
if ova_file.endswith('.zst'):
2393-
if subprocess.call(['zstd', '-d', ova_file, '-o', qcow_name]) != 0:
2394-
fatal("zstd extraction failed")
2395-
elif ova_file.endswith('.xz'):
2396-
with open(qcow_name, 'wb') as f:
2397-
if subprocess.call(['xz', '-d', '-c', ova_file], stdout=f) != 0:
2398-
fatal("xz extraction failed")
2399-
extract_duration = time.time() - extract_start_time
2400-
debuglog(config['debug'], "Extraction took {:.2f} seconds".format(extract_duration))
2401-
2402-
if not os.path.exists(qcow_name):
2403-
fatal("Extraction failed")
2404-
2405-
# Delete zst from data-dir immediately after extraction
2406-
debuglog(config['debug'], "Removing zst file: {}".format(ova_file))
2407-
try:
2408-
os.remove(ova_file)
2409-
except OSError as e:
2410-
debuglog(config['debug'], "Failed to remove zst file: {}".format(e))
2411-
2412-
# Copy qcow2 to cache
2413-
debuglog(config['debug'], "Copying qcow2 to cache: {} -> {}".format(qcow_name, cached_qcow2))
2414-
log("Caching extracted image: {}".format(cached_qcow2))
2415-
shutil.copy2(qcow_name, cached_qcow2)
2362+
cached_qcow2 = None
2363+
if config.get('cachedir'):
2364+
rel_path = os.path.relpath(output_dir, working_dir)
2365+
cache_output_dir = os.path.join(config['cachedir'], rel_path)
2366+
if not os.path.exists(cache_output_dir):
2367+
debuglog(config['debug'], "Creating cache directory: {}".format(cache_output_dir))
2368+
os.makedirs(cache_output_dir)
2369+
cached_qcow2 = os.path.join(cache_output_dir, os.path.basename(qcow_name))
2370+
2371+
if config['snapshot'] and cached_qcow2 and os.path.exists(cached_qcow2):
2372+
debuglog(config['debug'], "Snapshot mode: Using cached qcow2 directly: {}".format(cached_qcow2))
2373+
qcow_name = cached_qcow2
2374+
elif not os.path.exists(qcow_name):
2375+
if cached_qcow2 and os.path.exists(cached_qcow2):
2376+
# Cache hit: copy qcow2 from cache to data-dir
2377+
debuglog(config['debug'], "Found cached qcow2: {}".format(cached_qcow2))
2378+
log("Copying cached image: {} -> {}".format(cached_qcow2, qcow_name))
2379+
start_time = time.time()
2380+
shutil.copy2(cached_qcow2, qcow_name)
2381+
duration = time.time() - start_time
2382+
debuglog(config['debug'], "Copying from cache took {:.2f} seconds".format(duration))
24162383
else:
2417-
# No cache-dir: download zst, extract, delete zst
2384+
# Cache miss or no cache-dir: download and extract
24182385
if not os.path.exists(ova_file):
24192386
if download_file(zst_link, ova_file, config['debug']):
24202387
download_optional_parts(zst_link, ova_file, debug=config['debug'])
24212388

24222389
if not os.path.exists(ova_file):
2423-
fatal("Image file not found: " + ova_file)
2424-
2390+
fatal("Failed to download image: " + ova_file)
2391+
24252392
log("Extracting " + ova_file)
24262393
extract_start_time = time.time()
24272394
if ova_file.endswith('.zst'):
@@ -2442,6 +2409,19 @@ def find_image_link(releases, target_zst, target_xz):
24422409
os.remove(ova_file)
24432410
except OSError:
24442411
pass
2412+
2413+
if cached_qcow2:
2414+
# Copy qcow2 to cache
2415+
debuglog(config['debug'], "Copying qcow2 to cache: {} -> {}".format(qcow_name, cached_qcow2))
2416+
log("Caching extracted image: {}".format(cached_qcow2))
2417+
shutil.copy2(qcow_name, cached_qcow2)
2418+
if config['snapshot']:
2419+
debuglog(config['debug'], "Snapshot mode: Removing extracted qcow2 from data-dir and using cache")
2420+
try:
2421+
os.remove(qcow_name)
2422+
except OSError:
2423+
pass
2424+
qcow_name = cached_qcow2
24452425

24462426
# Key files
24472427
vm_name = "{}-{}".format(config['os'], config['release'])
@@ -2587,6 +2567,9 @@ def find_image_link(releases, target_zst, target_xz):
25872567
"-drive", "file={},format=qcow2,if={}".format(qcow_name, disk_if)
25882568
])
25892569

2570+
if config['snapshot']:
2571+
args_qemu.append("-snapshot")
2572+
25902573
# Windows on ARM has DirectSound issues; disable audio only there.
25912574
if IS_WINDOWS and host_arch == "aarch64":
25922575
args_qemu.extend(["-audiodev", "none,id=snd"])

0 commit comments

Comments
 (0)