Files
cloud-hypervisor/.github/workflows/ci.yaml
Anatol Belski b0369bf2da ci: Bump virtio-villain to v0.5.5
Further stability improvements in the villain harness keep printed
verdicts when a batch times out, stop batch timeouts from wedging
benign tests, and capture VMM stderr separately so it cannot
corrupt verdict markers, reducing spurious WEDGED results.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-17 13:38:04 +00:00

970 lines
39 KiB
YAML

name: CI
on: [pull_request, merge_group]
permissions:
contents: read
pull-requests: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
cancel-in-progress: true
jobs:
preflight:
name: preflight
runs-on: ubuntu-latest
outputs:
full: ${{ steps.classify.outputs.full }}
rust: ${{ steps.changes.outputs.rust }}
cargo: ${{ steps.changes.outputs.cargo }}
openapi: ${{ steps.changes.outputs.openapi }}
dockerfile: ${{ steps.changes.outputs.dockerfile }}
shell: ${{ steps.changes.outputs.shell }}
ci: ${{ steps.changes.outputs.ci }}
docs: ${{ steps.changes.outputs.docs }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- id: changes
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
with:
filters: |
rust:
- '**/*.rs'
- 'build.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- 'rust-toolchain.toml'
cargo:
- '**/Cargo.toml'
- '**/Cargo.lock'
openapi:
- 'vmm/src/api/openapi/**'
dockerfile:
- 'resources/Dockerfile'
shell:
- '**/*.sh'
- 'scripts/**'
ci:
- '.github/workflows/**'
docs:
- 'docs/**'
- '**/*.md'
- '.github/ISSUE_TEMPLATE/**'
- 'LICENSES/**'
- 'CODEOWNERS'
- id: classify
name: Classify changes
run: |
set -eufo pipefail
full=false
if [[ "${{ steps.changes.outputs.rust }}" == "true" \
|| "${{ steps.changes.outputs.dockerfile }}" == "true" \
|| "${{ steps.changes.outputs.shell }}" == "true" \
|| "${{ steps.changes.outputs.ci }}" == "true" ]]; then
full=true
fi
echo "full=$full" >> "$GITHUB_OUTPUT"
echo "full=$full"
dco:
name: dco
needs: [preflight]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Set up Python 3.x
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Check DCO
if: github.event_name == 'pull_request'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eufo pipefail
pip3 install -U dco-check
dco-check -e "49699333+dependabot[bot]@users.noreply.github.com"
gitlint:
name: gitlint
needs: [preflight]
# PR-only: gitlint needs GITHUB_BASE_REF, unset on merge_group.
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
# PR head, not the merge ref, so gitlint sees the PR's commits.
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Set up Python 3.10
uses: actions/setup-python@v6
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade gitlint
- name: Lint git commit messages
run: |
gitlint --commits "origin/$GITHUB_BASE_REF.."
lychee:
name: lychee
needs: [preflight]
if: needs.preflight.outputs.docs == 'true' || needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Get changed files in PR
id: changed-files
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
with:
base_sha: ${{ github.event.pull_request.base.sha }}
- name: Verify Changed Files
run: |
set -eufo pipefail
echo "--- tj-actions/changed-files Outputs ---"
echo "any_changed: ${{ steps.changed-files.outputs.any_changed }}"
echo "all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }}"
echo "added_files: ${{ steps.changed-files.outputs.added_files }}"
echo "modified_files: ${{ steps.changed-files.outputs.modified_files }}"
echo "deleted_files: ${{ steps.changed-files.outputs.deleted_files }}"
echo "renamed_files: ${{ steps.changed-files.outputs.renamed_files }}"
echo "----------------------------------------"
if [ -n "${{ steps.changed-files.outputs.all_changed_files }}" ]; then
echo "Detected changes: all_changed_files output is NOT empty."
else
echo "No changes detected: all_changed_files output IS empty."
fi
- name: Link Availability Check (Diff Only)
if: ${{ steps.changed-files.outputs.all_changed_files != '' }}
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
with:
args: --verbose --config .lychee.toml ${{ steps.changed-files.outputs.all_changed_files }}
failIfEmpty: false
fail: true
taplo:
name: taplo
needs: [preflight]
if: needs.preflight.outputs.cargo == 'true'
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install build dependencies
run: sudo apt-get update && sudo apt-get -yqq install build-essential libssl-dev
- name: Install taplo
run: cargo install taplo-cli --locked
- name: Check formatting
run: taplo fmt --check
audit:
name: audit
needs: [preflight]
if: needs.preflight.outputs.cargo == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions-rust-lang/audit@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
shlint:
name: shlint
needs: [preflight]
if: needs.preflight.outputs.shell == 'true' || needs.preflight.outputs.ci == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Run the shell script checkers
uses: luizm/action-sh-checker@883217215b11c1fabbf00eb1a9a041f62d74c744 # v0.10.0
env:
SHFMT_OPTS: -i 4 -d
SHELLCHECK_OPTS: -x --source-path scripts
hadolint:
name: hadolint
needs: [preflight]
if: needs.preflight.outputs.dockerfile == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Lint Dockerfile
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
with:
dockerfile: ./resources/Dockerfile
format: tty
no-fail: false
verbose: true
failure-threshold: info
reuse:
name: reuse
needs: [preflight]
if: needs.preflight.outputs.full == 'true' || needs.preflight.outputs.cargo == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: REUSE Compliance Check
uses: fsfe/reuse-action@v6
formatting:
name: formatting
needs: [preflight]
if: needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
rust: [nightly]
target:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-musl
env:
RUSTFLAGS: -D warnings
steps:
- name: Code checkout
uses: actions/checkout@v7
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
components: rustfmt
- name: Formatting (rustfmt)
run: cargo fmt --all -- --check
- name: Formatting (fuzz) (rustfmt)
run: cargo fmt --all --manifest-path fuzz/Cargo.toml -- --check
package-consistency:
name: package-consistency
needs: [preflight]
if: needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install dependencies
run: sudo apt install -y python3
- name: Install Rust toolchain stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Check Rust VMM Package Consistency of root Workspace
run: python3 scripts/package-consistency-check.py github.com/rust-vmm
- name: Check Rust VMM Package Consistency of fuzz Workspace
run: |
set -eufo pipefail
pushd fuzz
python3 ../scripts/package-consistency-check.py github.com/rust-vmm
popd
fuzz-build:
name: fuzz-build
needs: [preflight]
if: needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
rust: [nightly]
target: [x86_64-unknown-linux-gnu]
env:
RUSTFLAGS: -D warnings
steps:
- name: Code checkout
uses: actions/checkout@v7
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- name: Install Cargo fuzz
run: cargo install cargo-fuzz
- name: Fuzz Build
run: cargo fuzz build
- name: Fuzz Check
run: cargo fuzz check
openapi:
name: openapi
needs: [preflight]
if: needs.preflight.outputs.openapi == 'true'
runs-on: ubuntu-latest
container: openapitools/openapi-generator-cli
steps:
- uses: actions/checkout@v7
- name: Validate OpenAPI
run: |
/usr/local/bin/docker-entrypoint.sh validate -i vmm/src/api/openapi/cloud-hypervisor.yaml
typos:
name: typos
needs: [preflight]
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: crate-ci/typos@bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # v1.48.0
quality:
name: quality
needs: [preflight]
if: needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
# Beta clippy is non-blocking; continue-on-error below keeps the
# aggregated needs.quality.result green when only beta fails.
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
rust:
- beta
- stable
target:
- aarch64-unknown-linux-gnu
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
include:
- rust: beta
experimental: true
- rust: stable
experimental: false
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
components: clippy
- name: Bisectability Check (default features)
if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }}
run: |
set -eufo pipefail
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
git checkout ${{ github.sha }}
- name: Clippy (kvm)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings
- name: Clippy (mshv)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings
- name: Clippy (mshv + kvm)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings
- name: Clippy (default features)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples -- -D warnings
- name: Clippy (default features + guest_debug)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings
- name: Clippy (default features + pvmemcontrol)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings
- name: Clippy (default features + tracing)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings
- name: Clippy (default features + fw_cfg)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "fw_cfg" -- -D warnings
- name: Clippy (default features + ivshmem)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "ivshmem" -- -D warnings
- name: Clippy (kvm + sev_snp)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm,sev_snp" -- -D warnings
- name: Clippy (mshv + sev_snp)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,sev_snp" -- -D warnings
- name: Clippy (mshv + igvm + sev_snp)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,igvm,sev_snp" -- -D warnings
- name: Clippy (kvm + igvm)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm,igvm" -- -D warnings
- name: Clippy (mshv + igvm)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,igvm" -- -D warnings
- name: Clippy (kvm + igvm + sev_snp + fw_cfg)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm,igvm,sev_snp,fw_cfg" -- -D warnings
- name: Clippy (default features + sev_snp + igvm + fw_cfg)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "sev_snp,igvm,fw_cfg" -- -D warnings
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"
build:
name: build
needs: [preflight]
if: needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust:
- stable
- beta
- nightly
- "1.89.0" # MSRV — keep quoted.
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install musl-gcc
run: sudo apt install -y musl-tools
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- name: Build (default features)
run: cargo build --locked --bin cloud-hypervisor
- name: Build (kvm)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm"
- name: Build (default features + dbus_api)
run: cargo build --locked --bin cloud-hypervisor --features "dbus_api"
- name: Build (default features + guest_debug)
run: cargo build --locked --bin cloud-hypervisor --features "guest_debug"
- name: Build (default features + pvmemcontrol)
run: cargo build --locked --bin cloud-hypervisor --features "pvmemcontrol"
- name: Build (default features + fw_cfg)
run: cargo build --locked --bin cloud-hypervisor --features "fw_cfg"
- name: Build (default features + ivshmem)
run: cargo build --locked --bin cloud-hypervisor --features "ivshmem"
- name: Build (mshv)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv"
- name: Build (mshv + igvm)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,igvm"
- name: Build (mshv + sev_snp)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,sev_snp"
- name: Build (mshv + igvm + sev_snp)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,igvm,sev_snp"
- name: Build (kvm + sev_snp)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm,sev_snp"
- name: Build (kvm + igvm + sev_snp + fw_cfg)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm,igvm,sev_snp,fw_cfg"
- name: Build (kvm + igvm)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm,igvm"
- name: Build (mshv + kvm)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm"
- name: Release Build (default features)
run: cargo build --locked --all --release --target=${{ matrix.target }}
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"
build-riscv64:
name: build-riscv64
needs: [preflight]
if: needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust:
- stable
- "1.89.0" # MSRV — keep quoted.
env:
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install riscv64 cross linker
run: sudo apt-get update && sudo apt-get install -y gcc-riscv64-linux-gnu
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: riscv64gc-unknown-linux-gnu
- name: Build (kvm)
run: cargo build --locked --package cloud-hypervisor --no-default-features --features "kvm" --target riscv64gc-unknown-linux-gnu
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"
# garm-jammy + gnu: runs on PR and MQ. Other 3 matrix entries are in
# integration-x86-64-mq (sibling, MQ-only, runs in parallel).
integration-x86-64-pr:
name: integration-x86-64-pr
needs: [preflight, dco, quality, build]
if: >-
needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
timeout-minutes: 80
env:
# Our runner has 16 cores (nproc).
# We limit parallelism only to avoid exhausting disk space and memory
# resources, not to save CPU resources.
PARALLEL_INTEGRATION_TESTS_NUM: 12
runs-on: garm-jammy-16
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install Docker
run: |
set -eufo pipefail
sudo apt-get update
sudo apt-get -y install ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt install -y docker-ce docker-ce-cli
- name: Prepare for VDPA
run: scripts/prepare_vdpa.sh
- name: Run unit tests
run: scripts/dev_cli.sh tests --unit --libc gnu
- name: Load openvswitch module
run: sudo modprobe openvswitch
- name: Run integration tests
timeout-minutes: 60
run: scripts/dev_cli.sh tests --integration --libc gnu
# MQ-only: the 3 matrix entries that integration-x86-64-pr does not cover.
integration-x86-64-mq:
name: integration-x86-64-mq
needs: [preflight, dco, quality, build]
if: >-
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
timeout-minutes: 80
env:
# Our runner has 16 cores (nproc).
# We limit parallelism only to avoid exhausting disk space and memory
# resources, not to save CPU resources.
PARALLEL_INTEGRATION_TESTS_NUM: 12
strategy:
fail-fast: false
matrix:
include:
- {runner: garm-jammy, libc: musl}
- {runner: garm-jammy-amd, libc: gnu}
- {runner: garm-jammy-amd, libc: musl}
# format() because `${{ matrix.runner }}-16` is not valid in runs-on.
runs-on: ${{ format('{0}-16', matrix.runner) }}
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install Docker
run: |
set -eufo pipefail
sudo apt-get update
sudo apt-get -y install ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt install -y docker-ce docker-ce-cli
- name: Prepare for VDPA
run: scripts/prepare_vdpa.sh
- name: Run unit tests
run: scripts/dev_cli.sh tests --unit --libc ${{ matrix.libc }}
- name: Load openvswitch module
run: sudo modprobe openvswitch
- name: Run integration tests
timeout-minutes: 60
run: scripts/dev_cli.sh tests --integration --libc ${{ matrix.libc }}
integration-arm64:
name: integration-arm64
needs: [preflight, dco, quality, build]
if: >-
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
timeout-minutes: 120
env:
# Our runner has 80 cores (nproc).
# We limit parallelism only to avoid exhausting disk space and memory
# resources, not to save CPU resources.
PARALLEL_INTEGRATION_TESTS_NUM: 25
runs-on: bookworm-arm64
steps:
# arm64 runner user is "runner" (vfio's is "github-runner").
- name: Fix workspace permissions
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Run unit tests (musl)
run: scripts/dev_cli.sh tests --unit --libc musl
- name: Load openvswitch module
run: sudo modprobe openvswitch
- name: Run integration tests (musl)
timeout-minutes: 60
run: scripts/dev_cli.sh tests --integration --libc musl
- name: Install Azure CLI
run: |
set -eufo pipefail
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
echo "deb [arch=arm64] https://packages.microsoft.com/repos/azure-cli/ bookworm main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
sudo apt update
sudo apt install -y azure-cli
- name: Download Windows image
shell: bash
run: |
set -eufo pipefail
IMG_BASENAME=windows-11-iot-enterprise-aarch64.raw
IMG_PATH=$HOME/workloads/$IMG_BASENAME
IMG_GZ_PATH=$HOME/workloads/$IMG_BASENAME.gz
IMG_GZ_BLOB_NAME=windows-11-iot-enterprise-aarch64-25h2-6.raw.gz
cp "scripts/$IMG_BASENAME.sha1" "$HOME/workloads/"
pushd "$HOME/workloads"
if sha1sum "$IMG_BASENAME.sha1" --check; then
exit
fi
popd
mkdir -p "$HOME/workloads"
rm -f "$IMG_PATH" "$IMG_GZ_PATH"
az storage blob download --container-name private-images --file "$IMG_GZ_PATH" --name "$IMG_GZ_BLOB_NAME" --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
gzip -d "$IMG_GZ_PATH"
- name: Run Windows guest integration tests
timeout-minutes: 30
run: scripts/dev_cli.sh tests --integration-windows --libc musl
integration-vfio:
name: integration-vfio
needs: [preflight, dco, quality, build]
if: >-
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
runs-on: vfio-nvidia
env:
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
steps:
# vfio-nvidia runner user is "github-runner" (not "runner" like arm64).
- name: Fix workspace permissions
run: sudo chown -R github-runner:github-runner "${GITHUB_WORKSPACE}"
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Run VFIO integration tests
timeout-minutes: 25
run: scripts/dev_cli.sh tests --integration-vfio
# Most tests are failing with musl, see #6790
# - name: Run VFIO integration tests for musl
# timeout-minutes: 25
# run: scripts/dev_cli.sh tests --integration-vfio --libc musl
integration-windows:
name: integration-windows
needs: [preflight, dco, quality, build]
if: >-
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
runs-on: garm-jammy-16
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install Docker
run: |
set -eufo pipefail
sudo apt-get update
sudo apt-get -y install ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt install -y docker-ce docker-ce-cli
- name: Install Azure CLI
run: |
set -eufo pipefail
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
sudo apt update
sudo apt install -y azure-cli
- name: Download Windows image
run: |
set -eufo pipefail
mkdir $HOME/workloads
az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2025-amd64-1.raw" --name windows-server-2025-amd64-1.raw --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
- name: Run Windows guest integration tests
timeout-minutes: 15
run: scripts/dev_cli.sh tests --integration-windows
- name: Run Windows guest integration tests for musl
timeout-minutes: 15
run: scripts/dev_cli.sh tests --integration-windows --libc musl
integration-mshv-x86-64:
name: integration-mshv-x86-64
needs: [preflight, dco, quality, build]
if: >-
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
timeout-minutes: 35
runs-on: mshv
steps:
# mshv runner user is "lsgunner"
- name: Fix workspace and Docker socket permissions
run: |
sudo chown -R lsgrunner:lsgrunner ${GITHUB_WORKSPACE}
sudo chmod 666 /var/run/docker.sock
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Prepare for VDPA
run: scripts/prepare_vdpa.sh
- name: Run integration tests
timeout-minutes: 30
run: scripts/dev_cli.sh tests --integration
# Rate-limiter host is not available
# integration-rate-limiter:
# name: integration-rate-limiter
# needs: [preflight, dco, quality, build]
# if: >-
# github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
# runs-on: bare-metal-9950x
# env:
# AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
# steps:
# - name: Code checkout
# uses: actions/checkout@v7
# with:
# fetch-depth: 0
# - name: Run rate-limiter integration tests
# timeout-minutes: 20
# run: scripts/dev_cli.sh tests --integration-rate-limiter
integration-sev-snp:
name: integration-sev-snp
needs: [preflight, dco, quality, build]
if: >-
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
timeout-minutes: 30
runs-on: noble-sevsnp
steps:
# Self-hosted runners reuse their workdir; a previous privileged
# container run can leave root-owned files behind.
- name: Fix workspace permissions
run: sudo chown -R "$(id -un):$(id -gn)" "${GITHUB_WORKSPACE}"
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Sanity-check SEV-SNP prerequisites
run: |
set -eufo pipefail
echo "Checking hypervisor device nodes..."
test -e /dev/kvm || { echo "::error::/dev/kvm missing"; exit 1; }
test -e /dev/sev || { echo "::error::/dev/sev missing"; exit 1; }
echo "Checking staged IGVM/kernel artifacts..."
test -d /usr/share/cloud-hypervisor/cvm \
|| { echo "::error::/usr/share/cloud-hypervisor/cvm missing"; exit 1; }
ls -l /usr/share/cloud-hypervisor/cvm
- name: Run CVM (SEV-SNP) integration tests
timeout-minutes: 20
run: scripts/dev_cli.sh tests --integration-cvm --hypervisor kvm
# Rate-limiter host is not available
# integration-rate-limiter:
# name: integration-rate-limiter
# needs: [preflight, dco, quality, build]
# if: >-
# github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
# runs-on: bare-metal-9950x
# env:
# AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
# steps:
# - name: Code checkout
# uses: actions/checkout@v7
# with:
# fetch-depth: 0
# - name: Run rate-limiter integration tests
# timeout-minutes: 20
# run: scripts/dev_cli.sh tests --integration-rate-limiter
virtio-villain:
name: virtio-villain
needs: [preflight, dco, quality, build]
if: needs.preflight.outputs.full == 'true'
timeout-minutes: 60
runs-on: ubuntu-latest
env:
VILLAIN_REPO: https://github.com/weltling/virtio-villain.git
VILLAIN_REF: v0.5.5
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Verify KVM is available
run: |
set -eufo pipefail
test -e /dev/kvm || { echo "::error::/dev/kvm missing on runner"; exit 1; }
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install dependencies
run: |
set -eufo pipefail
sudo apt-get update
sudo apt-get install -y musl-tools cpio gzip python3
sudo apt-get install -y virtiofsd || true
- name: Build cloud-hypervisor (kvm)
run: cargo build --locked --release --bin cloud-hypervisor --no-default-features --features kvm
- name: Clone virtio-villain
id: villain-src
run: |
set -eufo pipefail
git clone "$VILLAIN_REPO" virtio-villain
git -C virtio-villain checkout "$VILLAIN_REF"
echo "sha=$(git -C virtio-villain rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Cache virtio-villain build
id: villain-cache
uses: actions/cache@v6
with:
path: virtio-villain/target
key: virtio-villain-${{ runner.os }}-${{ runner.arch }}-${{ steps.villain-src.outputs.sha }}
- name: Build virtio-villain initramfs
if: steps.villain-cache.outputs.cache-hit != 'true'
run: make -C virtio-villain -j"$(nproc)" initramfs
- name: Run virtio-villain suite
working-directory: virtio-villain
run: |
set -eufo pipefail
mkdir -p villain-logs
sudo ./run \
--vmm "${GITHUB_WORKSPACE}/target/release/cloud-hypervisor" \
--blk-queues 2 --net-queues 2 --cpus 2 --memory 256M \
--order=fast \
--exclude S0094 --exclude PCI0006 \
--jobs 4 --batch 10 --timeout 45 --log-dir villain-logs \
--format junit --output villain-logs/results.xml \
| tee villain-logs/run.out
- name: Publish results to run summary
if: always()
working-directory: virtio-villain
run: |
set -eufo pipefail
{
echo '## virtio-villain'
echo '```'
if [ -f villain-logs/run.out ]; then
sed -n '/tests passed/,$p' villain-logs/run.out
else
echo 'no results (suite did not produce output)'
fi
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload virtio-villain logs
if: always()
uses: actions/upload-artifact@v7
with:
name: virtio-villain-logs
path: virtio-villain/villain-logs
if-no-files-found: ignore
# The single required-status check. Branch protection requires this one job.
all-green:
name: all-green
needs:
- audit
- build
- build-riscv64
- dco
- formatting
- fuzz-build
- gitlint
- hadolint
- integration-arm64
- integration-sev-snp
- integration-vfio
- integration-mshv-x86-64
- integration-windows
- integration-x86-64-mq
- integration-x86-64-pr
- openapi
- package-consistency
- preflight
- quality
- reuse
- shlint
- taplo
- typos
if: always()
runs-on: ubuntu-latest
steps:
- name: Verify all dependencies succeeded or were skipped
env:
NEEDS_JSON: ${{ toJson(needs) }}
run: |
set -eufo pipefail
echo "$NEEDS_JSON" | jq .
# success or skipped = pass; failure or cancelled = red.
echo "$NEEDS_JSON" | jq -e '
to_entries
| map(select(.value.result != "success" and .value.result != "skipped"))
| length == 0
' >/dev/null