Replace existing CI files with a consolidated one to delay starting
resource intensive CI jobs until after less resource intensive ones have
completed: e.g. don't start integration tests if the basic build tests
fail.
Architecture:
- Trigger on [pull_request, merge_group]; concurrency keyed per PR/ref.
- preflight job classifies changed paths and exposes a 'full' flag that
gates the heavier build/quality/integration suite. Any CI change
classifies itself as full so it is exercised.
- Leaf jobs gate at the job level using preflight outputs; doc-only,
openapi-only, dockerfile-only and similar PRs skip the full suite.
- integration-x86-64-pr runs the (garm-jammy, gnu) slice on PR and MQ;
integration-x86-64-mq runs the other 3 matrix entries on MQ only.
- integration-{arm64, vfio, windows, rate-limiter} are MQ-only.
- integration jobs gate on dco/quality/build success.
- A single all-green aggregator job is the only required-status check;
it folds in every leaf job via `needs`.
Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
Add support for backend that is connected via the vhost-user-generic
frontend to generate an interrupt into the guest when it has made a
change to the configuration. This is useful for devices that can change
the exposed configuration at runtime.
Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
Verify that a freshly created sparse QCOW2 image reports a
physical size smaller than its logical size.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Verify that try_clone preserves the backend dispatch for both
sync and io_uring backends.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Verify that the sync backend disables batch requests and the
io_uring backend enables them.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Delete QcowDiskSync and QcowDiskAsync wrapper structs along with
their DiskFile trait impls. Only the AsyncIo worker structs
QcowSync and QcowAsync remain. Reduce module visibility of
qcow_sync and qcow_async to pub(crate).
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Replace QcowDiskSync and QcowDiskAsync constructors in the
qcow_sync and qcow_async test modules with QcowDisk::new,
passing use_io_uring=false and use_io_uring=true respectively.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Update open_qcow2 to construct QcowDisk instead of choosing
between QcowDiskAsync and QcowDiskSync. The backend decision
is now made inside QcowDisk::create_async_io.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Replace QcowDiskSync and QcowDiskAsync with QcowDisk in all QCOW2
benchmark helpers. The sync helpers pass use_io_uring=false, the
async helpers pass use_io_uring=true.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Introduce QcowDisk, a unified DiskFile implementation for QCOW2
disk images that handles backend selection at runtime via a
use_io_uring flag, matching the pattern used by FixedVhdDisk.
The wrapper delegates to QcowSync or QcowAsync based on the flag
and includes a compile time guard that returns an error when
io_uring is requested but the feature is not enabled.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
The VirtioInterrupt is now always created so the Option<..> can always
be removed.
As a side effect the interrupt_source_group can also be removed from the
struct.
Signed-off-by: Rob Bradford <rbradford@meta.com>
Since this is always created there is no need to make it an Option type
simplifying the code. Historically it was an Option to support INTx
based virtio but that was removed long ago.
Signed-off-by: Rob Bradford <rbradford@meta.com>
msix_num is guaranteed to be at least 1 so this check (and the Option)
that it returns can be removed.
Signed-off-by: Rob Bradford <rbradford@meta.com>
Rather than calculate in the DeviceManager and pass it through do it in
the device where it already has all the required information.
Signed-off-by: Rob Bradford <rbradford@meta.com>
Previously the interrupt was created in VirtioPciDevice, moved via the
Option::take() to the VirtioPciDeviceActivator and then moved to the
VirtioDevice upon activation. On reset it would be moved back ready for
reactivation.
Since this already an Arc type remove the wrapping Option and instead
refcount it such that the VirtioPciDevice can continue to hold onto it
for later activations.
This significantly simplifies the reset() logic as there is no need to
hand back the interrupt.
A few devices used whether the interrupt was Some to make triggering an
interrupt a no-op. However the MSI-X interrupt routing already drops the
interrupt if the driver hasn't yet configured the vector so it is safe
to trigger the interrupt before device activation (e.g. balloon resize
request before driver loaded).
VirtioCommon still retains an Option<..> for the interrupt as the
interrupt is not known until activation time (after this has been
created). A helper VirtioCommon::trigger_interrupt() has been added to
handle this.
Signed-off-by: Rob Bradford <rbradford@meta.com>
Per the virtio spec a device reset must return the device to its
power-on state. The reset path was only zeroing queue_select. Add
VirtioPciCommonConfig::reset() and call it from the transport's reset
path so the configuration is cleared. Also relax the condition to allow
the device to be reset at any time to match the spec.
Signed-off-by: Rob Bradford <rbradford@meta.com>
The virtio specification treats reset as the recovery operation and so
must take the device back to a fresh state, and the driver waits for the
status read-back to converge before continuing. There is no defined way
for the device to report a reset failure to the driver.
Previously the implementations of reset() would return early and not
complete all their cleanup leaving them in an inconsistent state. Now
log errors and continue through the execution.
Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
The four vhost-user device wrappers (blk, fs, generic_vhost_user, net)
each carried an identical reset() body that resumed the worker thread,
asked the backend to reset, signalled kill_evt and dropped interrupt_cb.
Move the shared body into VhostUserCommon::reset() so behaviour stays in
one place.
No behavioural change.
Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
`test_live_migration_virtio_fs` and its `_local` variant lived in
`common_sequential` because they shared `~/workloads/shared_dir` as
the virtiofsd backing and wrote/deleted the same `migration_test_file`
and `post_migration_file` paths inside it. Two instances running
concurrently would race on those files.
Give each invocation its own backing directory under `guest.tmp_dir`,
which is already per-test unique and gets cleaned up by the `TempDir`
drop. The test logic is otherwise unchanged. Move both wrappers and
the helper from `common_sequential` to `common_parallel` and update
the sequential-tests comment accordingly.
The boot footprint is small (512 MB src + 512 MB dest), so two
concurrent instances comfortably fit alongside the rest of the
parallel suite. The remaining sequential live-migration tests
(balloon, NUMA) genuinely need their isolation slot for memory
headroom.
Signed-off-by: Rob Bradford <rbradford@meta.com>
Reduce `nr_hugepages` from 12 GB to 6 GB on both architectures. The
number if huge pages needed (if all the tests run at once) is 4GiB so
this gives 50% headroom.
This should reduce the number of tests that fail/flake out due to lack
of memory.
Signed-off-by: Rob Bradford <rbradford@meta.com>
Move the live migration tests themselves into the common scopes allowing
the tests to now run interleaved together hopefully reducing CI time.
On MSHV the live migration tests are now not compiled in rather than
compiled in and skipped (as the helpers are not compiled in for MSHV.)
Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
Move the helper methods used for live migration to the common utils
(like many other tests use).
Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
Move the live migration test running from their own script into the
x86-64 script (on aarch64 they were already in the same script.) They
were historically separate as they were new. Now they are established it
makes sense for them to be combined.
The timeout in the GitHub workflow has been extended to accommodate the
extra work in the same step.
The Rust test scopes are unchanged - the running of the tests has been
moved.
Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
The aarch64 integration script hardcoded `LAST_RELEASE_VERSION="v39.0"`
for the live-upgrade binary download, while the live-migration runner
already accepts a `MIGRATABLE_VERSION` env override with a `vxx.0`
regex check. Standardise the aarch64 script on the same env-override
block so both arches honour the same knob with the same validation.
Default is unchanged (v39.0).
Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
This daemon is only a testing tool used during integration testing. The
previous code auto-detected the image type from the file's magic bytes
and opened qcow2 images via QcowFile. Such behaviour has been the cause
of security issues in the past.
Drop the qcow2 detection and open path so only raw images are handled.
Signed-off-by: Rob Bradford <rbradford@meta.com>
The GvaGpaValid flag in the intercept message indicates whether
the provided GPA corresponds to the decoded GVA. Without checking
this flag, the emulator may incorrectly use a stale GPA mapping
when the hypervisor invalidates it.
Add a check for the GvaGpaValid flag before using the cached
(GVA, GPA) mapping. If the flag is clear, use a sentinel value
to force translate() to perform a proper hypercall-based
translation instead of using an invalid cached mapping.
Signed-off-by: Pedro Barbuda <pbarbuda@microsoft.com>
Signed-off-by: Muminul Islam <muislam@microsoft.com>
release_memory_range took (range_base, range_len) verbatim from a
guest-controlled descriptor (free-page-reporting and inflate paths) and
called fallocate(PUNCH_HOLE | KEEP_SIZE) on the backing file before any
length check ran. find_region only validates that range_base lands
inside *some* region; range_len can extend past the region's end. When
the operator uses --memory-zone file=PATH against a host file larger
than the zone, the punch zeroes host file content past the guest's
memory extent.
Free-page reporting is advisory, so the VMM is allowed to act on a
subset of a free range. Clamp range_len to the bytes that actually fit
within the region returned by find_region. Going past the end of a
region is most likely a guest bug so log it.
Signed-off-by: Dylan Reid <dgreid@fb.com>
# TL;DR
In https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7990 we
replaced the old deadlock with another deadlock. This commit finally
resolves (hopefully) all dead locks on that code path by not holding
`CpuManager::vcpu_states` while waiting for vCPU pause acknowledgements.
A vCPU can receive the pause kick while servicing the ACPI CPU hotplug
MMIO device, and that MMIO path also needs `vcpu_states`. Holding the
mutex across the wait phase deadlocks pause against that MMIO access.
# Problem
`signal_vcpus()` used to lock `CpuManager::vcpu_states` for the whole
function, signal every vCPU, and then wait for each vCPU to acknowledge
the kick.
That lock scope is too wide. A vCPU is allowed to observe the kick in
userspace rather than returning directly from `KVM_RUN`. During boot,
`vcpu0` can be in an MMIO access on the ACPI CPU hotplug device when
pause arrives. `AcpiCpuHotplugController::read()` and `write()` both
lock `vcpu_states` to inspect or update the selected vCPU state.
The deadlock looks like this:
VMM thread vCPU thread
---------- ----------
lock(vcpu_states)
signal_vcpus()
wait for ack ---------------------> receives pause kick
enters ACPI CPU hotplug MMIO
lock(vcpu_states) [blocks]
wait for ack <--------------------- cannot set vcpu_run_interrupted
The VMM thread waits for `vcpu_run_interrupted` to flip, but the vCPU
cannot reach the pause acknowledgement path because it is sleeping on
the same mutex.
The debug logs matched that cycle exactly: signal delivery kept
working, `vcpu0` stayed in one unmatched `run()` invocation, the stuck
thread sampled in `futex_do_wait`, and the backtrace pointed at
`AcpiCpuHotplugController::read()`.
# Reproducer
This was reproducible by continuously issuing `pause()` / `resume()`
from while a Linux guest was still booting. That boot-tim window
reliably exercises the ACPI CPU hotplug MMIO access that participates in
the deadlock. Once the guest had finished booting, the problem became
much harder to trigger (as there is no MMIO operation without explicit
CPU plugging).
# Solution
Keep the existing two-phase behavior so all vCPUs are still signalled
before the wait phase, but narrow the lifetime of the `vcpu_states`
mutex. Reacquire it only long enough to access one `VcpuState` at a
time in each phase.
That preserves the original pause semantics and the fast signal-all /
wait-all structure, while removing the lock inversion with the ACPI CPU
hotplug MMIO path.
This also remains safe if a vCPU is hot-removed while pause is in
progress. Hot-remove does not shrink `vcpu_states`; it stops the thread
and clears the `VcpuState` handle in place. `signal_vcpus()` can
therefore snapshot the vector length up front, and if a vCPU disappears
between the signal and wait phases,
`wait_until_signal_acknowledged()` will observe `handle.is_none()` and
return successfully.
The interruption handshake itself lives in atomics inside each
`VcpuState`. The outer mutex is only needed to reach the state objects,
not to keep the acknowledgement protocol correct. Dropping the mutex
between iterations therefore does not weaken the pause protocol, but it
does allow MMIO handlers and other `vcpu_states` users to make forward
progress while the VMM waits for the kick to be observed.
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
Increase the number of parallel integration tests in CI to save ~3-5
minutes per x86_64 run. The thread limit is driven by RAM and disk
space constraints, not CPU availability.
A new `PARALLEL_INTEGRATION_TESTS_NUM` environment variable controls
the thread count. In CI it is set explicitly (12 for x86_64, 25 for
ARM64); locally it falls back to `nproc / 4`, preserving the previous
behavior.
Only the first test group (`common_parallel`, `live_migration_parallel`)
uses the overridden value - subsequent groups (dbus_api, fw_cfg,
ivshmem, aarch64_acpi) continue to use the `nproc / 4` default.
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
The physical size of a fixed VHD is the data region plus the 512
byte footer. Verify it differs from the logical size.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Fixed VHDs do not support resize. Verify the error is returned.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Verify that a cloned disk produces the same async I/O backend
as the original for both sync and io_uring paths.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Verify that create_async_io dispatches to the correct backend
depending on use_io_uring. The sync backend does not support
batch requests, while the io_uring backend does.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Verify that FixedVhdDisk::new with the sync backend reads the VHD
footer and reports the correct logical size.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Update open_fixed_vhd to construct FixedVhdDisk instead of choosing
between FixedVhdDiskAsync and FixedVhdDiskSync. The io_uring decision
is now made inside FixedVhdDisk::create_async_io().
Remove FixedVhdDiskSync and FixedVhdDiskAsync DiskFile wrapper structs
from fixed_vhd_sync.rs and fixed_vhd_async.rs. Only the FixedVhdSync
and FixedVhdAsync AsyncIo worker structs remain in those files.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Introduce FixedVhdDisk as a single DiskFile wrapper around FixedVhd.
It accepts a use_io_uring flag at construction time and dispatches to
FixedVhdSync or FixedVhdAsync inside create_async_io() accordingly.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Verify that physical size of a sparse file is less than
logical size.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Verify that try_clone preserves the backend variant for
each RawBackend: sync, AIO and io_uring.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Verify each RawBackend variant stores correctly and
create_async_io dispatches to the matching constructor.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Verify that RawDisk with the sync backend reports the correct
logical size for a temporary file. Introduce make_raw_file helper
and TEST_SIZE constant for reuse in subsequent tests.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Update open_raw to construct RawDisk instead of choosing between
RawFileDisk, RawFileDiskSync and RawFileDiskAio. The backend decision
is now made inside RawDisk::create_async_io.
Remove the DiskFile wrapper structs from raw_sync.rs, raw_async.rs
and raw_async_aio.rs. Only the AsyncIo worker structs remain in those
files. Reduce their module visibility to pub(crate).
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Replace direct RawFileAsyncAio construction with RawDisk
and create_async_io, consistent with the unified API.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Update the fuzz target to use RawDisk instead of RawFileDiskSync,
consistent with the unified API.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>