exec-sandbox 0.21.0__tar.gz → 0.22.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/PKG-INFO +1 -1
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/pyproject.toml +1 -1
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/build-qemu.sh +6 -2
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/constants.py +17 -3
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/migration_client.py +36 -18
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/qemu_cmd.py +72 -5
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/scheduler.py +13 -5
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_migration_client.py +49 -16
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_scheduler.py +301 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/.editorconfig +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/.github/actions/build-guest-agent/action.yml +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/.github/actions/build-gvproxy-wrapper/action.yml +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/.github/actions/build-tiny-init/action.yml +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/.github/actions/build-vm-images/action.yml +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/.github/actions/setup-vm-infra/action.yml +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/.github/runs-on.yml +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/.github/workflows/release.yml +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/.github/workflows/test.yml +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/.gitignore +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/CLAUDE.md +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/LICENSE +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/Makefile +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/README.md +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/SECURITY.md +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/cicd/version.sh +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/.gitignore +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/Cargo.lock +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/Cargo.toml +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/Makefile +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/connection.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/constants.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/error.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/file_io.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/init.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/main.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/oom_guard.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/packages.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/repl/execute.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/repl/mod.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/repl/scripts/js_repl.mjs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/repl/scripts/python_repl.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/repl/scripts/shell_repl.sh +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/repl/spawn.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/repl/wrappers.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/types.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/guest-agent/src/validation.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/gvproxy-wrapper/.gitignore +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/gvproxy-wrapper/Makefile +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/gvproxy-wrapper/go.mod +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/gvproxy-wrapper/go.sum +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/gvproxy-wrapper/main.go +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/images/kernel/alpine-virt-aarch64.config +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/images/kernel/alpine-virt-x86_64.config +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/images/kernel/exec-sandbox.config +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/benchmark_density.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/benchmark_latency.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/benchmark_optimizer.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/build-guest-agent.sh +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/build-images.sh +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/build-initramfs.sh +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/build-kernel.sh +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/build-qcow2.sh +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/build-tiny-init.sh +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/build_package_catalogs.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/ci-resource-monitor.sh +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/ci_diagnose.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/extract-kernel.sh +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/extract-vmlinux.sh +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/scripts/upgrade-versions.sh +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/__init__.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/_imports.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/_logging.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/admission.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/aio_utils.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/asset_downloader.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/assets.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/balloon_client.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/base_cache_manager.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/cgroup.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/cli.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/config.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/disk_snapshot_manager.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/dns_filter.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/exceptions.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/guest_agent_protocol.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/guest_channel.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/gvproxy.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/hash_utils.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/lock_utils.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/memory_snapshot_manager.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/models.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/overlay_pool.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/package_validator.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/permission_utils.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/platform_utils.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/port_forward.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/process_registry.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/py.typed +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/qemu_storage_daemon.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/qemu_vm.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/qmp_exchange.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/resource_cleanup.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/resources/npm_top_10k.json +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/resources/pypi_top_10k.json +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/session.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/settings.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/socket_auth.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/subprocess_utils.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/system_probes.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/validation.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/vm_manager.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/vm_timing.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/vm_types.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/vm_working_directory.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/src/exec_sandbox/warm_vm_pool.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/stubs/backports/__init__.pyi +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/stubs/backports/zstd.pyi +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/__init__.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/benchmarks/__init__.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/benchmarks/test_bench_core.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/conftest.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/cpu_helpers.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_admission.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_aio_utils.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_asset_downloader.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_assets.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_asyncio_cleanup.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_balloon_client.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_balloon_integration.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_bun_ffi_security.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_cgroup.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_cli.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_config.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_consume_stream.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_dask_compatibility.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_device_security.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_disk_snapshot_manager.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_dns_filter.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_dns_filtering.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_dns_resolv_hijack.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_ech_proxy_behavior.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_env_var_security.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_etc_hardening.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_execution_edge_cases.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_file_io_buffers.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_file_io_edge_cases.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_file_io_integrity.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_file_io_security.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_file_io_streaming.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_fork_output.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_fork_stdout.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_guest_agent_integration.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_guest_agent_protocol.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_image_tools.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_int_str_digits.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_kernel_attack_surface.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_loopback.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_memory_leaks.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_memory_optimization.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_memory_snapshot_manager.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_multiprocessing.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_nonroot_repl.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_orphan_vm_cpu.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_overlay_pool.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_package_validator.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_payload_integrity.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_permission_utils.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_pid1_zombie_reaping.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_platform_utils.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_port_forward.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_process_registry.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_psutil_compatibility.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_qemu_attack_surface.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_qemu_storage_daemon.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_resource_cleanup.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_rng_l1_snapshot.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_rng_quality.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_session.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_session_guard_and_io.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_signal_isolation.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_socket_auth.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_speculative_execution.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_streaming.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_subprocess_utils.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_vm_capacity.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_vm_manager.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_vm_working_directory.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_warm_vm_pool.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_wasm_compatibility.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tests/test_write_worker.py +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tiny-init/.gitignore +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tiny-init/Cargo.lock +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tiny-init/Cargo.toml +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tiny-init/Makefile +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tiny-init/src/device.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tiny-init/src/main.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/tiny-init/src/sys.rs +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/uv.lock +0 -0
- {exec_sandbox-0.21.0 → exec_sandbox-0.22.0}/versions.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: exec-sandbox
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.22.0
|
|
4
4
|
Summary: Secure code execution in microVMs with QEMU
|
|
5
5
|
Project-URL: Homepage, https://github.com/dualeai/exec-sandbox
|
|
6
6
|
Project-URL: Documentation, https://github.com/dualeai/exec-sandbox#readme
|
|
@@ -101,8 +101,12 @@ cd "qemu-${QEMU_VERSION}"
|
|
|
101
101
|
# --enable-fdt: device tree support, required for aarch64-softmmu (virt machine),
|
|
102
102
|
# harmless for x86_64. Needs libfdt-dev.
|
|
103
103
|
# --enable-kvm: KVM acceleration support.
|
|
104
|
-
# --enable-linux-io-uring: io_uring async I/O backend
|
|
105
|
-
#
|
|
104
|
+
# --enable-linux-io-uring: io_uring async I/O backend. Needs liburing-dev (>= 0.3).
|
|
105
|
+
# Two benefits: (1) block drives use aio=io_uring (falls back to aio=threads if
|
|
106
|
+
# unavailable at runtime), and (2) since QEMU 10.2 the main event loop itself is
|
|
107
|
+
# io_uring-based when liburing is linked — lower syscall/wakeup overhead on fd
|
|
108
|
+
# monitoring. Keeping this flag ensures BOTH; without it the main loop silently
|
|
109
|
+
# falls back to ppoll. (probe_io_uring_support() only gates the drive aio= mode.)
|
|
106
110
|
# --enable-seccomp: seccomp sandbox for QEMU process (-sandbox on).
|
|
107
111
|
# Needs libseccomp-dev (>= 2.3.0).
|
|
108
112
|
# --enable-slirp: user-mode networking backend. Needs libslirp-dev.
|
|
@@ -402,17 +402,31 @@ Only used when socket activation is unavailable (sudo wraps QEMU, closing
|
|
|
402
402
|
inherited fds). With socket activation, the socket is pre-created and no
|
|
403
403
|
polling is needed."""
|
|
404
404
|
|
|
405
|
-
MEMORY_SNAPSHOT_MIN_QEMU_VERSION: Final[tuple[int, int, int]] = (
|
|
405
|
+
MEMORY_SNAPSHOT_MIN_QEMU_VERSION: Final[tuple[int, int, int]] = (11, 0, 0)
|
|
406
406
|
"""Minimum QEMU version for mapped-ram + multifd migration capabilities.
|
|
407
|
-
Changing capabilities requires bumping MEMORY_SNAPSHOT_FORMAT_VERSION."""
|
|
408
407
|
|
|
409
|
-
|
|
408
|
+
Floor is 11.0: it is the first release where mapped-ram coexists with
|
|
409
|
+
x-ignore-shared without a null-deref ("Bad address") crash, letting the template
|
|
410
|
+
and full-RAM paths share ONE unified capability list (no version fork). The
|
|
411
|
+
project pins QEMU 11.0.2, so this floor is free; older local installs fall back
|
|
412
|
+
to plain streaming migration. Changing capabilities requires bumping
|
|
413
|
+
MEMORY_SNAPSHOT_FORMAT_VERSION."""
|
|
414
|
+
|
|
415
|
+
MEMORY_SNAPSHOT_FORMAT_VERSION: Final[int] = 4
|
|
410
416
|
"""Migration format version. Bump when changing QMP capabilities or migration
|
|
411
417
|
protocol to invalidate stale L1 cache entries. History:
|
|
412
418
|
v1: Plain streaming format (no mapped-ram)
|
|
413
419
|
v2: mapped-ram + multifd (QEMU >= 9.0)
|
|
414
420
|
v3: x-ignore-shared VM templating — device-state-only vmstate + separate
|
|
415
421
|
RAM file for COW sharing across VMs (QEMU >= 9.0)
|
|
422
|
+
v4: unified capability list — template path now also enables mapped-ram +
|
|
423
|
+
multifd alongside x-ignore-shared (QEMU >= 11.0 fixes the coexistence
|
|
424
|
+
crash), removing the version-forked branch.
|
|
425
|
+
|
|
426
|
+
NOTE: the migration stream layout is QEMU-version- AND parameter-specific — a
|
|
427
|
+
QEMU upgrade or a change to multifd-channels / direct-io (both baked into the
|
|
428
|
+
mapped-ram file layout) requires restore-QEMU == save-QEMU. Any such change must
|
|
429
|
+
bump this version so stale, incompatible snapshots are not restored.
|
|
416
430
|
"""
|
|
417
431
|
|
|
418
432
|
# ============================================================================
|
|
@@ -197,28 +197,37 @@ class MigrationClient:
|
|
|
197
197
|
|
|
198
198
|
Both sender (save) and receiver (restore) MUST set the same
|
|
199
199
|
capabilities, otherwise QEMU rejects with "received capability is off".
|
|
200
|
+
|
|
201
|
+
NOTE (design constraints, verified QEMU 11.0):
|
|
202
|
+
- mapped-ram is INCOMPATIBLE with multifd-compression (seekable-fd
|
|
203
|
+
requirement) AND with migration TLS. Any snapshot compression or
|
|
204
|
+
encryption for a future S3 freeze/thaw path must therefore be done
|
|
205
|
+
EXTERNALLY (client-side zstd + AEAD), not via QEMU capabilities. The
|
|
206
|
+
sanctioned in-QEMU perf recovery is the `direct-io` migration parameter
|
|
207
|
+
(O_DIRECT), gated to the full-RAM path when that lands.
|
|
208
|
+
- CPR (cpr-reboot/transfer/exec) is NOT a resume-anywhere primitive: all
|
|
209
|
+
modes are same-host only (fd/RAM handoff to a peer QEMU), so they cannot
|
|
210
|
+
back S3 freeze/thaw. Their only use here would be in-place QEMU upgrades.
|
|
211
|
+
- The migration stream format is QEMU-version-specific; a QEMU bump
|
|
212
|
+
invalidates saved snapshots (see MEMORY_SNAPSHOT_FORMAT_VERSION).
|
|
200
213
|
"""
|
|
201
214
|
if qemu_version is None or qemu_version < constants.MEMORY_SNAPSHOT_MIN_QEMU_VERSION:
|
|
202
215
|
return
|
|
203
216
|
|
|
217
|
+
# Unified capability list (QEMU >= 11.0). Base is mapped-ram + multifd for
|
|
218
|
+
# seekable, parallel page I/O. Template mode adds x-ignore-shared to skip
|
|
219
|
+
# RAM pages backed by the shared memory-backend-file (device-state-only
|
|
220
|
+
# vmstate). mapped-ram + x-ignore-shared coexist safely only since 11.0
|
|
221
|
+
# (earlier releases null-deref on ignored blocks — the reason the floor is
|
|
222
|
+
# 11.0), so both paths share one branch-free list.
|
|
223
|
+
# Ref: QEMU commit "migration/mapped-ram: Fix x-ignore-shared snapshots"
|
|
224
|
+
# (Pawel Zmarzly, Nov 2025; reviewed by Peter Xu, Dec 2025)
|
|
225
|
+
capabilities = [
|
|
226
|
+
{"capability": "multifd", "state": True},
|
|
227
|
+
{"capability": "mapped-ram", "state": True},
|
|
228
|
+
]
|
|
204
229
|
if use_template:
|
|
205
|
-
|
|
206
|
-
# memory-backend-file. mapped-ram is INCOMPATIBLE with x-ignore-shared
|
|
207
|
-
# in QEMU <= 10.x: mapped-ram's bitmap code dereferences null pointers
|
|
208
|
-
# for ignored blocks, crashing with "Bad address". Fix merged for 11.0.
|
|
209
|
-
# Ref: QEMU commit "migration/mapped-ram: Fix x-ignore-shared snapshots"
|
|
210
|
-
# (Pawel Zmarzly, Nov 2025; reviewed by Peter Xu, Dec 2025)
|
|
211
|
-
# TODO(qemu-11): Re-enable mapped-ram + multifd alongside x-ignore-shared
|
|
212
|
-
# once QEMU >= 11.0 is the minimum version. This will give parallel I/O
|
|
213
|
-
# for the device-state portion of the vmstate.
|
|
214
|
-
capabilities = [
|
|
215
|
-
{"capability": "x-ignore-shared", "state": True},
|
|
216
|
-
]
|
|
217
|
-
else:
|
|
218
|
-
capabilities = [
|
|
219
|
-
{"capability": "multifd", "state": True},
|
|
220
|
-
{"capability": "mapped-ram", "state": True},
|
|
221
|
-
]
|
|
230
|
+
capabilities.append({"capability": "x-ignore-shared", "state": True})
|
|
222
231
|
resp = await self._execute(
|
|
223
232
|
"migrate-set-capabilities",
|
|
224
233
|
{"capabilities": capabilities},
|
|
@@ -263,6 +272,11 @@ class MigrationClient:
|
|
|
263
272
|
# Valid = false). QEMU's hvf_handle_exception asserts isv==true, causing
|
|
264
273
|
# SIGABRT. Stopping the vCPU first eliminates the race.
|
|
265
274
|
# See: https://github.com/qemu/qemu/blob/master/target/arm/hvf/hvf.c
|
|
275
|
+
#
|
|
276
|
+
# NOTE (non-adoption): the background-snapshot capability (live snapshot
|
|
277
|
+
# without pausing the vCPU) is not used. It is KVM-only (needs userfaultfd
|
|
278
|
+
# write-protect) so excluded on HVF, and we snapshot idle sessions where a
|
|
279
|
+
# brief stop is cheap and full-fidelity capture wants a quiescent VM anyway.
|
|
266
280
|
resp = await self._execute("stop")
|
|
267
281
|
if "error" in resp:
|
|
268
282
|
raise MigrationTransientError(f"stop command failed: {resp['error']}")
|
|
@@ -346,7 +360,11 @@ class MigrationClient:
|
|
|
346
360
|
status = resp.get("return", {}).get("status", "")
|
|
347
361
|
if status == "completed":
|
|
348
362
|
return
|
|
349
|
-
|
|
363
|
+
# "failing" (QEMU 11.0+): an error occurred and cleanup is underway;
|
|
364
|
+
# the status will transition to "failed" shortly. Treat it as terminal
|
|
365
|
+
# here so a mid-stream failure surfaces immediately instead of burning
|
|
366
|
+
# another poll interval waiting for the "failed" transition.
|
|
367
|
+
if status in ("failed", "cancelled", "failing"):
|
|
350
368
|
error_desc = resp.get("return", {}).get("error-desc", "unknown")
|
|
351
369
|
raise MigrationTransientError(f"Migration {status}: {error_desc}")
|
|
352
370
|
|
|
@@ -89,6 +89,10 @@ async def build_qemu_cmd( # noqa: PLR0912, PLR0915
|
|
|
89
89
|
|
|
90
90
|
# Detect hardware acceleration type (centralized in detect_accel_type)
|
|
91
91
|
accel_type = await detect_accel_type(force_emulation=settings.force_emulation)
|
|
92
|
+
|
|
93
|
+
# QEMU version gates several flags below (machine props, netdev reconnect,
|
|
94
|
+
# -run-with). Probe once up front; the result is cached in system_probes.
|
|
95
|
+
qemu_version = await probe_qemu_version()
|
|
92
96
|
logger.info(
|
|
93
97
|
"Hardware acceleration detection",
|
|
94
98
|
extra={"vm_id": vm_id, "accel_type": accel_type.value, "is_macos": is_macos},
|
|
@@ -102,6 +106,14 @@ async def build_qemu_cmd( # noqa: PLR0912, PLR0915
|
|
|
102
106
|
else:
|
|
103
107
|
# TCG software emulation fallback (~5-8x slower than KVM/HVF)
|
|
104
108
|
#
|
|
109
|
+
# NOTE (security envelope): QEMU's security policy grants the
|
|
110
|
+
# "virtualization use case" isolation guarantee only to microvm (x86) /
|
|
111
|
+
# virt (aarch64) *with* a hardware accelerator (KVM/HVF). The TCG path is
|
|
112
|
+
# explicitly NOT security-supported ("must not rely on QEMU for
|
|
113
|
+
# isolation"). It is a functional fallback for CI / trusted workloads
|
|
114
|
+
# only; prod isolation always runs on KVM or HVF.
|
|
115
|
+
# See: https://qemu-project.gitlab.io/qemu/system/security.html
|
|
116
|
+
#
|
|
105
117
|
# thread=single: Disable MTTCG to reduce thread count per VM. Without this,
|
|
106
118
|
# each VM creates multiple threads for parallel translation, exhausting
|
|
107
119
|
# system thread limits when running parallel tests (qemu_thread_create:
|
|
@@ -149,10 +161,19 @@ async def build_qemu_cmd( # noqa: PLR0912, PLR0915
|
|
|
149
161
|
# random.trust_cpu=on in kernel cmdline
|
|
150
162
|
# mem-merge: KSM page dedup (Linux only; macOS HVF does not support it)
|
|
151
163
|
mem_merge = "off" if is_macos else "on"
|
|
164
|
+
# virtio-mmio-transports=8: cap the number of virtio-mmio transport slots
|
|
165
|
+
# (QEMU virt board default is 32). We instantiate at most 6 virtio-mmio
|
|
166
|
+
# devices (blk-base + optional blk-snap + serial + balloon + rng +
|
|
167
|
+
# optional net); 8 leaves headroom. Fewer empty slots means the guest's
|
|
168
|
+
# virtio_mmio driver skips ~24 spurious MMIO magic/device-id probes at
|
|
169
|
+
# boot and the DTB is smaller. Keep >= the live device count or QEMU aborts.
|
|
170
|
+
# Machine property added in QEMU 11.0 — gated to avoid aborting older
|
|
171
|
+
# local installs. See docs/system/arm/virt.rst.
|
|
172
|
+
mmio_transports = ",virtio-mmio-transports=8" if qemu_version is not None and qemu_version >= (11, 0, 0) else ""
|
|
152
173
|
machine_type = (
|
|
153
|
-
f"virt,virtualization=off,highmem=off,gic-version=3,its=off,dtb-randomness=off,mem-merge={mem_merge}"
|
|
174
|
+
f"virt,virtualization=off,highmem=off,gic-version=3,its=off,dtb-randomness=off,mem-merge={mem_merge}{mmio_transports}"
|
|
154
175
|
if is_macos
|
|
155
|
-
else f"virt,virtualization=off,highmem=off,gic-version=3,its=off,dtb-randomness=off,mem-merge={mem_merge},dump-guest-core=off"
|
|
176
|
+
else f"virt,virtualization=off,highmem=off,gic-version=3,its=off,dtb-randomness=off,mem-merge={mem_merge}{mmio_transports},dump-guest-core=off"
|
|
156
177
|
)
|
|
157
178
|
else:
|
|
158
179
|
arch_suffix = "x86_64"
|
|
@@ -294,6 +315,9 @@ async def build_qemu_cmd( # noqa: PLR0912, PLR0915
|
|
|
294
315
|
# also lacks 256-bit vector ops (TCG_TARGET_HAS_v256=0), so AVX2
|
|
295
316
|
# is unavailable — effective ceiling is SSE4.2. On x86 hosts the
|
|
296
317
|
# effective ceiling is x86_64-v3 (AVX2/FMA).
|
|
318
|
+
# NOTE (non-adoption): ARM `max,sve=off,sme=off` (individually maskable
|
|
319
|
+
# on `max` since QEMU 11.0) is not set — boot never exercises SVE/SME,
|
|
320
|
+
# so the win is ~zero (only trims per-vCPU vector-state struct size).
|
|
297
321
|
# See: https://www.qemu.org/docs/master/system/i386/cpu.html
|
|
298
322
|
# See: https://gitlab.com/qemu-project/qemu/-/issues/844
|
|
299
323
|
(
|
|
@@ -339,6 +363,9 @@ async def build_qemu_cmd( # noqa: PLR0912, PLR0915
|
|
|
339
363
|
# (16-64KB per fault instead of 4KB), but transparent_hugepage=never
|
|
340
364
|
# disables anonymous 2MB hugepages (no khugepaged, no compaction stalls).
|
|
341
365
|
# File-backed large folios work regardless of this sysfs setting.
|
|
366
|
+
# NOTE (non-adoption): keeping anon THP off is deliberate for density —
|
|
367
|
+
# 2MB hugepages defeat free-page-reporting and KSM (both 4KB-granular)
|
|
368
|
+
# and add compaction stalls; enabling them would hurt host-RAM-per-VM.
|
|
342
369
|
+ " transparent_hugepage=never"
|
|
343
370
|
# Boot verbosity: debug_boot enables full kernel/init logging for diagnostics
|
|
344
371
|
# Note: loglevel=7 overrides loglevel=1 set in console_params (kernel uses last occurrence)
|
|
@@ -471,9 +498,8 @@ async def build_qemu_cmd( # noqa: PLR0912, PLR0915
|
|
|
471
498
|
]
|
|
472
499
|
)
|
|
473
500
|
|
|
474
|
-
# Determine AIO mode
|
|
501
|
+
# Determine AIO mode based on cached startup probes (qemu_version probed above)
|
|
475
502
|
io_uring_available = await probe_io_uring_support()
|
|
476
|
-
qemu_version = await probe_qemu_version()
|
|
477
503
|
aio_mode = "io_uring" if io_uring_available else "threads"
|
|
478
504
|
if not io_uring_available:
|
|
479
505
|
logger.debug(
|
|
@@ -503,6 +529,13 @@ async def build_qemu_cmd( # noqa: PLR0912, PLR0915
|
|
|
503
529
|
|
|
504
530
|
# Disk configuration (EROFS base drive, serial=base)
|
|
505
531
|
# Uses qcow2 overlay backed by the EROFS base image
|
|
532
|
+
#
|
|
533
|
+
# NOTE (non-adoption): RWF_DSYNC FUA (QEMU 10.0 file-posix) is deliberately
|
|
534
|
+
# unused. It only helps cache=writethrough/directsync; cache=unsafe drops all
|
|
535
|
+
# flushes/FUA outright, which strictly dominates for discard-on-exit overlays
|
|
536
|
+
# (a host crash loses in-flight overlay writes, but the overlay is thrown away
|
|
537
|
+
# on VM exit). Switching to a flushing cache mode to gain FUA would be a large
|
|
538
|
+
# regression here.
|
|
506
539
|
qemu_args.extend(
|
|
507
540
|
[
|
|
508
541
|
"-drive",
|
|
@@ -534,6 +567,13 @@ async def build_qemu_cmd( # noqa: PLR0912, PLR0915
|
|
|
534
567
|
# rootfs drive, regardless of /dev/vdX assignment. ARM virt (MMIO) enumerates
|
|
535
568
|
# virtio devices in reverse declaration order, so /dev/vda != first -device on ARM.
|
|
536
569
|
# D2: event_idx=off reduces interrupt coalescing overhead during boot.
|
|
570
|
+
#
|
|
571
|
+
# NOTE (non-adoption): num-queues=1 is deliberate. virtio-blk multiqueue /
|
|
572
|
+
# iothread-vq-mapping (QEMU 9.0) only pays off with many vCPUs and an
|
|
573
|
+
# I/O-bound guest saturating a single iothread. Our VMs have 1-2 vCPUs, a
|
|
574
|
+
# read-only page-cache-hot EROFS base and a tiny cache=unsafe overlay — never
|
|
575
|
+
# iothread-CPU-bound. HVF has no ioeventfd (block already routed to main-loop
|
|
576
|
+
# above) and TCG is thread=single, so extra queues buy nothing here.
|
|
537
577
|
qs = constants.VIRTIO_QUEUE_SIZE
|
|
538
578
|
qemu_args.extend(
|
|
539
579
|
[
|
|
@@ -622,6 +662,10 @@ async def build_qemu_cmd( # noqa: PLR0912, PLR0915
|
|
|
622
662
|
# See: https://bugs.launchpad.net/qemu/+bug/1639791 (early virtio console lost)
|
|
623
663
|
# See: https://gist.github.com/mcastelino/aa118275991d4f561ee22dc915b9345f
|
|
624
664
|
# =============================================================
|
|
665
|
+
# NOTE (non-adoption): guest-agent transport stays on virtio-serial. vhost-vsock
|
|
666
|
+
# was evaluated as an alternative; no QEMU 10.1→11.0 reliability/perf change
|
|
667
|
+
# shifts the tradeoff, and virtio-serial's named ports (cmd/event) map cleanly
|
|
668
|
+
# onto the existing guest-agent protocol.
|
|
625
669
|
qemu_args.extend(
|
|
626
670
|
[
|
|
627
671
|
"-device",
|
|
@@ -637,6 +681,13 @@ async def build_qemu_cmd( # noqa: PLR0912, PLR0915
|
|
|
637
681
|
)
|
|
638
682
|
|
|
639
683
|
# virtio-balloon for host memory efficiency (deflate/inflate for warm pool).
|
|
684
|
+
#
|
|
685
|
+
# NOTE (non-adoption): virtio-mem is deliberately not used in place of the
|
|
686
|
+
# balloon. It targets long-lived, resizable VMs; unplug is guest-cooperative,
|
|
687
|
+
# slow, and can partially fail, and its block size must be a power-of-two
|
|
688
|
+
# >= 2 MiB — coarse for our small -m VMs. It has no HVF support and yields no
|
|
689
|
+
# net RSS win over free-page-reporting + template-COW page sharing.
|
|
690
|
+
#
|
|
640
691
|
# Disabled for ALL template VMs (save AND restore):
|
|
641
692
|
# 1. Device topology must match between save and restore — QEMU migration
|
|
642
693
|
# fails with "Unknown section" if one side has balloon and the other doesn't.
|
|
@@ -697,6 +748,9 @@ async def build_qemu_cmd( # noqa: PLR0912, PLR0915
|
|
|
697
748
|
#
|
|
698
749
|
# =============================================================
|
|
699
750
|
|
|
751
|
+
# NOTE (non-adoption): the in-tree passt netdev (QEMU 10.1) is not used. It
|
|
752
|
+
# lacks gvproxy's API-driven outbound-filtering and dynamic port-forward model
|
|
753
|
+
# that Modes 1-3 below depend on, so it is not a drop-in replacement.
|
|
700
754
|
needs_network = allow_network or bool(expose_ports)
|
|
701
755
|
if needs_network:
|
|
702
756
|
# All modes use socket networking to gvproxy (fast ~300ms boot)
|
|
@@ -783,8 +837,21 @@ async def build_qemu_cmd( # noqa: PLR0912, PLR0915
|
|
|
783
837
|
|
|
784
838
|
# Orphan protection: kill QEMU if parent process dies (QEMU 10.2+)
|
|
785
839
|
# Uses PR_SET_PDEATHSIG on Linux, kqueue on macOS/FreeBSD
|
|
840
|
+
#
|
|
841
|
+
# async-teardown=on (Linux only): fork a teardown clone that unmaps guest RAM
|
|
842
|
+
# so the main QEMU can exit without blocking on munmap. Trims destroy_vm()
|
|
843
|
+
# tail latency under mass pool recycle. macOS QEMU rejects this sub-option
|
|
844
|
+
# (only chroot/exit-with-parent/user exist there), so it is Linux-gated.
|
|
845
|
+
#
|
|
846
|
+
# NOTE (non-adoption): -run-with chroot=/user= are not used. Both require a
|
|
847
|
+
# privileged launcher (QEMU starts root, then drops), which conflicts with our
|
|
848
|
+
# unprivileged model — sudo -u qemu-vm + fd-passing (socket-activated QMP).
|
|
849
|
+
# Filesystem confinement is already provided by `unshare --mount` on Linux.
|
|
786
850
|
if qemu_version is not None and qemu_version >= (10, 2, 0):
|
|
787
|
-
|
|
851
|
+
run_with_opts = "exit-with-parent=on"
|
|
852
|
+
if not is_macos:
|
|
853
|
+
run_with_opts += ",async-teardown=on"
|
|
854
|
+
qemu_args.extend(["-run-with", run_with_opts])
|
|
788
855
|
|
|
789
856
|
# Run QEMU as unprivileged user if qemu-vm user is available (optional hardening)
|
|
790
857
|
# Falls back to current user if qemu-vm doesn't exist - VM still provides isolation
|
|
@@ -217,6 +217,13 @@ class Scheduler:
|
|
|
217
217
|
|
|
218
218
|
# Initialize WarmVMPool (optional)
|
|
219
219
|
if self.config.warm_pool_size > 0:
|
|
220
|
+
# The pool pre-boots one VM per language, and its startup hashes
|
|
221
|
+
# each base image (check_cache -> compute_cache_key -> get_base_image).
|
|
222
|
+
# On a fresh cache ensure_assets() fetched only kernel/initramfs, so
|
|
223
|
+
# download every pooled base image first, else start() raises
|
|
224
|
+
# VmDependencyError. Mirrors the per-run fetch in _prepare_vm.
|
|
225
|
+
if self.config.auto_download_assets:
|
|
226
|
+
await asyncio.gather(*(fetch_base_image(language.value) for language in Language))
|
|
220
227
|
self._warm_pool = WarmVMPool(
|
|
221
228
|
self._vm_manager,
|
|
222
229
|
self.config,
|
|
@@ -701,6 +708,12 @@ class Scheduler:
|
|
|
701
708
|
if vm is None:
|
|
702
709
|
is_cold_boot = True
|
|
703
710
|
|
|
711
|
+
# Base image must be on disk before the L1 cache key hashes it
|
|
712
|
+
# (check_cache -> compute_cache_key -> get_base_image) and before
|
|
713
|
+
# cold create_vm. Skip when snapshot_path already fetched it.
|
|
714
|
+
if self.config.auto_download_assets and not snapshot_path:
|
|
715
|
+
await fetch_base_image(language)
|
|
716
|
+
|
|
704
717
|
admission_start = asyncio.get_running_loop().time()
|
|
705
718
|
async with self._vm_manager.reservation_context(
|
|
706
719
|
vm_id=f"{constants.SCHEDULER_TENANT_ID}-{task_id}",
|
|
@@ -747,11 +760,6 @@ class Scheduler:
|
|
|
747
760
|
|
|
748
761
|
# Cold boot fallback: L1 miss (or L1 skipped for network/boot-log)
|
|
749
762
|
if vm is None:
|
|
750
|
-
# Auto-download base image if needed (skip when snapshot_path
|
|
751
|
-
# is set — _get_or_create_snapshot already fetched it)
|
|
752
|
-
if self.config.auto_download_assets and not snapshot_path:
|
|
753
|
-
await fetch_base_image(language)
|
|
754
|
-
|
|
755
763
|
vm = await self._vm_manager.create_vm(
|
|
756
764
|
language=language,
|
|
757
765
|
tenant_id=constants.SCHEDULER_TENANT_ID,
|
|
@@ -287,11 +287,11 @@ class TestMigrationClientExecuteEdgeCases:
|
|
|
287
287
|
class TestMigrationClientSetCapabilities:
|
|
288
288
|
"""Tests for _set_capabilities (mapped-ram + multifd on QEMU >= 9.0)."""
|
|
289
289
|
|
|
290
|
-
async def
|
|
291
|
-
"""QEMU >=
|
|
290
|
+
async def test_enables_mapped_ram_for_qemu_11(self) -> None:
|
|
291
|
+
"""QEMU >= 11.0 should enable mapped-ram + multifd."""
|
|
292
292
|
client, _reader, writer = _make_connected([{"return": {}}])
|
|
293
293
|
|
|
294
|
-
await client._set_capabilities((
|
|
294
|
+
await client._set_capabilities((11, 2, 0))
|
|
295
295
|
|
|
296
296
|
cmds = writer.commands
|
|
297
297
|
assert len(cmds) == 1
|
|
@@ -299,12 +299,25 @@ class TestMigrationClientSetCapabilities:
|
|
|
299
299
|
cap_names = {c["capability"] for c in cmds[0]["arguments"]["capabilities"]}
|
|
300
300
|
assert "multifd" in cap_names
|
|
301
301
|
assert "mapped-ram" in cap_names
|
|
302
|
+
# Non-template save: no x-ignore-shared (full RAM captured).
|
|
303
|
+
assert "x-ignore-shared" not in cap_names
|
|
304
|
+
|
|
305
|
+
async def test_template_adds_x_ignore_shared(self) -> None:
|
|
306
|
+
"""Template mode appends x-ignore-shared to the unified mapped-ram list."""
|
|
307
|
+
client, _reader, writer = _make_connected([{"return": {}}])
|
|
308
|
+
|
|
309
|
+
await client._set_capabilities((11, 2, 0), use_template=True)
|
|
310
|
+
|
|
311
|
+
cmds = writer.commands
|
|
312
|
+
assert len(cmds) == 1
|
|
313
|
+
cap_names = {c["capability"] for c in cmds[0]["arguments"]["capabilities"]}
|
|
314
|
+
assert cap_names == {"multifd", "mapped-ram", "x-ignore-shared"}
|
|
302
315
|
|
|
303
316
|
async def test_no_capabilities_for_old_qemu(self) -> None:
|
|
304
|
-
"""QEMU <
|
|
317
|
+
"""QEMU < 11.0 should not set capabilities (falls back to plain streaming)."""
|
|
305
318
|
client, _reader, writer = _make_connected()
|
|
306
319
|
|
|
307
|
-
await client._set_capabilities((
|
|
320
|
+
await client._set_capabilities((10, 2, 0))
|
|
308
321
|
|
|
309
322
|
assert len(writer.commands) == 0
|
|
310
323
|
|
|
@@ -325,25 +338,25 @@ class TestMigrationClientSetCapabilities:
|
|
|
325
338
|
)
|
|
326
339
|
|
|
327
340
|
# Should not raise
|
|
328
|
-
await client._set_capabilities((
|
|
341
|
+
await client._set_capabilities((11, 2, 0))
|
|
329
342
|
|
|
330
343
|
|
|
331
344
|
class TestMigrationClientSetCapabilitiesEdgeCases:
|
|
332
345
|
"""Edge cases for capability negotiation."""
|
|
333
346
|
|
|
334
|
-
async def
|
|
335
|
-
"""Exactly QEMU
|
|
347
|
+
async def test_boundary_qemu_version_11_0_0(self) -> None:
|
|
348
|
+
"""Exactly QEMU 11.0.0 should enable capabilities (>= check)."""
|
|
336
349
|
client, _reader, writer = _make_connected([{"return": {}}])
|
|
337
350
|
|
|
338
|
-
await client._set_capabilities((
|
|
351
|
+
await client._set_capabilities((11, 0, 0))
|
|
339
352
|
|
|
340
353
|
assert any(c["execute"] == "migrate-set-capabilities" for c in writer.commands)
|
|
341
354
|
|
|
342
|
-
async def
|
|
343
|
-
"""QEMU
|
|
355
|
+
async def test_boundary_qemu_version_10_99_99(self) -> None:
|
|
356
|
+
"""QEMU 10.99.99 should NOT enable capabilities (< 11.0.0)."""
|
|
344
357
|
client, _reader, writer = _make_connected()
|
|
345
358
|
|
|
346
|
-
await client._set_capabilities((
|
|
359
|
+
await client._set_capabilities((10, 99, 99))
|
|
347
360
|
|
|
348
361
|
assert len(writer.commands) == 0
|
|
349
362
|
|
|
@@ -406,6 +419,26 @@ class TestMigrationClientPollMigration:
|
|
|
406
419
|
):
|
|
407
420
|
await client._poll_migration(timeout=5.0)
|
|
408
421
|
|
|
422
|
+
async def test_poll_raises_on_failing(self) -> None:
|
|
423
|
+
"""Poll should raise on 'failing' status (QEMU 11.0+ error-cleanup state).
|
|
424
|
+
|
|
425
|
+
'failing' means an error occurred and cleanup is underway (it transitions
|
|
426
|
+
to 'failed' shortly). We treat it as terminal so the failure surfaces one
|
|
427
|
+
poll interval sooner instead of waiting for the 'failed' transition.
|
|
428
|
+
"""
|
|
429
|
+
client, _reader, _writer = _make_connected(
|
|
430
|
+
[
|
|
431
|
+
{"return": {"status": "active"}},
|
|
432
|
+
{"return": {"status": "failing", "error-desc": "write error"}},
|
|
433
|
+
]
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
with (
|
|
437
|
+
patch.object(constants, "MEMORY_SNAPSHOT_POLL_INTERVAL_SECONDS", 0.001),
|
|
438
|
+
pytest.raises(MigrationTransientError, match="failing"),
|
|
439
|
+
):
|
|
440
|
+
await client._poll_migration(timeout=5.0)
|
|
441
|
+
|
|
409
442
|
async def test_poll_raises_on_query_error(self) -> None:
|
|
410
443
|
"""Poll should raise MigrationTransientError on query-migrate error response."""
|
|
411
444
|
client, _reader, _writer = _make_connected(
|
|
@@ -494,13 +527,13 @@ class TestMigrationClientSaveSnapshot:
|
|
|
494
527
|
)
|
|
495
528
|
|
|
496
529
|
with patch.object(constants, "MEMORY_SNAPSHOT_POLL_INTERVAL_SECONDS", 0.001):
|
|
497
|
-
await client.save_snapshot(Path("/tmp/test.vmstate"), qemu_version=(
|
|
530
|
+
await client.save_snapshot(Path("/tmp/test.vmstate"), qemu_version=(11, 2, 0))
|
|
498
531
|
|
|
499
532
|
cmds = [c["execute"] for c in writer.commands]
|
|
500
533
|
assert cmds == ["migrate-set-capabilities", "stop", "migrate", "query-migrate", "quit"]
|
|
501
534
|
|
|
502
535
|
async def test_save_command_sequence_old_qemu(self) -> None:
|
|
503
|
-
"""Save with QEMU <
|
|
536
|
+
"""Save with QEMU < 11.0 should: stop → migrate → poll → quit (no capabilities)."""
|
|
504
537
|
client, _reader, writer = _make_connected(
|
|
505
538
|
[
|
|
506
539
|
{"return": {}}, # stop
|
|
@@ -638,7 +671,7 @@ class TestMigrationClientRestoreSnapshot:
|
|
|
638
671
|
)
|
|
639
672
|
|
|
640
673
|
with patch.object(constants, "MEMORY_SNAPSHOT_POLL_INTERVAL_SECONDS", 0.001):
|
|
641
|
-
await client.restore_snapshot(Path("/tmp/test.vmstate"), qemu_version=(
|
|
674
|
+
await client.restore_snapshot(Path("/tmp/test.vmstate"), qemu_version=(11, 2, 0))
|
|
642
675
|
|
|
643
676
|
cmds = [c["execute"] for c in writer.commands]
|
|
644
677
|
assert cmds == ["migrate-set-capabilities", "migrate-incoming", "query-migrate", "cont"]
|
|
@@ -655,7 +688,7 @@ class TestMigrationClientRestoreSnapshot:
|
|
|
655
688
|
)
|
|
656
689
|
|
|
657
690
|
with patch.object(constants, "MEMORY_SNAPSHOT_POLL_INTERVAL_SECONDS", 0.001):
|
|
658
|
-
await client.restore_snapshot(Path("/tmp/test.vmstate"), qemu_version=(
|
|
691
|
+
await client.restore_snapshot(Path("/tmp/test.vmstate"), qemu_version=(11, 2, 0))
|
|
659
692
|
|
|
660
693
|
cmds = [c["execute"] for c in writer.commands]
|
|
661
694
|
assert "quit" not in cmds
|