Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
66494ee
Fix clippy warnings
ejc3 Dec 24, 2025
8aabb99
Add registry-based layer caching for container builds
ejc3 Dec 24, 2025
52660e3
Add zstd to container for kernel tarball extraction
ejc3 Dec 24, 2025
0ef4d2d
Update podman on buildjet runner for cache flag support
ejc3 Dec 24, 2025
a49f74a
Run VM tests directly on bare metal, not in container
ejc3 Dec 24, 2025
715359a
Remove ulimit nproc and pids-limit (not allowed in GitHub Actions)
ejc3 Dec 24, 2025
d786fb5
Run all CI jobs on buildjet bare metal
ejc3 Dec 24, 2025
213db09
Simplify Makefile test targets
ejc3 Dec 24, 2025
ff88f37
Simplify CI with feature-based test gating
ejc3 Dec 24, 2025
bfb4f0a
Add explicit setup command and --setup flag for on-demand setup
ejc3 Dec 24, 2025
6ab93bf
Add setup-fcvm Makefile target as prerequisite for tests
ejc3 Dec 24, 2025
86fe163
Update docs and Makefile for explicit setup workflow
ejc3 Dec 24, 2025
b0a293a
Add test speed tiers for faster CI iteration
ejc3 Dec 24, 2025
4ea3a49
Fix feature-based test tier gating for pjdfstest
ejc3 Dec 24, 2025
7de5dbb
Document build performance benchmarks
ejc3 Dec 24, 2025
1fbe1ef
Deduplicate README/DESIGN docs, fix inaccuracies
ejc3 Dec 24, 2025
9bbcc00
Refactor test tiers: only test-root uses sudo
ejc3 Dec 25, 2025
0bd1acb
Add parallel in-VM pjdfstest matrix, remove sequential tests
ejc3 Dec 25, 2025
8a9aee7
Update docs and cross-reference pjdfstest matrices
ejc3 Dec 25, 2025
24e4389
Simplify Containerfile and Makefile for rootless container tests
ejc3 Dec 25, 2025
e91ca1c
Fix DNAT port collision and increase test timeouts
ejc3 Dec 25, 2025
62c9527
Increase FUSE reader threads from 1 to 256 in fc-agent
ejc3 Dec 25, 2025
90c1953
Add content-addressable image cache for localhost images
ejc3 Dec 25, 2025
6bf03e3
Simplify CI to 2 runners with sequential targets
ejc3 Dec 25, 2025
8ac3cba
Fix CI: use ubuntu-latest for container job (podman needs systemd ses…
ejc3 Dec 25, 2025
a709a36
CI: enable FUSE user_allow_other for tests
ejc3 Dec 25, 2025
fdca9de
Use .local/ for tests to support hardlinks on overlayfs
ejc3 Dec 25, 2025
9bad28b
Use CARGO_MANIFEST_DIR for .local test paths
ejc3 Dec 25, 2025
a458b69
Add diagnostics on hardlink test failure
ejc3 Dec 25, 2025
55ceef4
Fix hardlink unit test to simulate real FUSE behavior
ejc3 Dec 25, 2025
a5e821b
Add diagnostics and skip hardlink tests on unsupported filesystems
ejc3 Dec 25, 2025
316aefb
Fix hardlink tests: detect and skip when AT_EMPTY_PATH unavailable
ejc3 Dec 25, 2025
71f5aa9
Factor out AT_EMPTY_PATH check to common helper
ejc3 Dec 25, 2025
6e9eca4
Create target and cargo-home dirs before container run
ejc3 Dec 25, 2025
e9978f3
Create /mnt/fcvm-btrfs dir for container tests
ejc3 Dec 25, 2025
905df6e
Remove cargo-home mount from container tests
ejc3 Dec 25, 2025
a00d198
Set CARGO_HOME to writable location in container
ejc3 Dec 25, 2025
5c99851
Remove target dir mount from container
ejc3 Dec 25, 2025
526891a
Simplify container tests: run as root, remove userns
ejc3 Dec 25, 2025
f888b37
CI: Run setup-fcvm as explicit step before VM tests
ejc3 Dec 25, 2025
1bbe3b2
CI: Run full test suite in Container job too
ejc3 Dec 25, 2025
caadc31
CI: Configure rootless podman with cgroupfs on buildjet
ejc3 Dec 25, 2025
89033a0
CI: Fix containers.conf format (use [engine] section)
ejc3 Dec 25, 2025
71a2f7f
CI: Use printf for containers.conf (fix heredoc indentation)
ejc3 Dec 25, 2025
1c74bef
Print serial log on setup timeout for debugging
ejc3 Dec 25, 2025
2bfed99
Stream Layer 2 setup serial output at info level
ejc3 Dec 25, 2025
64f70c7
Fix Layer 2 setup: proper error handling and package resolution
ejc3 Dec 25, 2025
b0654f7
Update docs to reflect Layer 2 setup improvements
ejc3 Dec 25, 2025
f12d7d9
README: Expand setup section with detailed steps
ejc3 Dec 25, 2025
053cec8
README: Document --setup flag for auto-setup on first run
ejc3 Dec 25, 2025
8a90821
README: Clarify why --setup is rootless only
ejc3 Dec 25, 2025
0210025
CI: Auto-cancel in-progress runs on new push
ejc3 Dec 25, 2025
e3e75bc
CI: Add missing dependencies to Container job
ejc3 Dec 25, 2025
5f1853a
Fix VM shutdown in Layer 2 setup
ejc3 Dec 25, 2025
4b640fa
CI: Run setup inside container, add sanity checks
ejc3 Dec 25, 2025
62aeb8c
CI: Run setup inside container, add sanity checks
ejc3 Dec 25, 2025
7e4fa8b
Fix podman-in-podman for rootless container setup
ejc3 Dec 25, 2025
8059098
CI: Add Rust cache for faster builds
ejc3 Dec 25, 2025
b30f67a
CI: Add cargo cache for container builds
ejc3 Dec 25, 2025
676388d
Fix: Add --cgroups=disabled to actual podman command
ejc3 Dec 25, 2025
b27bb30
Refactor: Use single source for download script
ejc3 Dec 25, 2025
9290255
Separate lint tests from integration tests
ejc3 Dec 25, 2025
f0b9cec
CI: Install cargo-audit/deny for CVSS 4.0 support
ejc3 Dec 25, 2025
d98ed1d
docs: Add NO HACKS policy to CLAUDE.md
ejc3 Dec 25, 2025
a83bdbe
CI: Add shared-key to rust-cache for cache reuse
ejc3 Dec 25, 2025
d2b678a
CI: Save rust cache even on failure
ejc3 Dec 25, 2025
8e714fd
Fix disk space exhaustion in CI snapshot tests
ejc3 Dec 25, 2025
0cad682
CI: Enable userfaultfd in Container job
ejc3 Dec 25, 2025
39512ce
CI: Create and pass /dev/userfaultfd to container
ejc3 Dec 25, 2025
e99bb18
CI: Pin cargo tool versions for CVSS 4.0 support
ejc3 Dec 25, 2025
dad0a79
Remove userfaultfd device check - sysctl is sufficient
ejc3 Dec 25, 2025
62ec598
Fix formatting
ejc3 Dec 25, 2025
f054ea5
Add automatic debug logging to test files with CI artifact upload
ejc3 Dec 25, 2025
04769e0
Print debug log path at end of each fcvm process
ejc3 Dec 25, 2025
903474a
Update CLAUDE.md with debug logging design
ejc3 Dec 25, 2025
5929893
Fix lint failures: fmt and cargo-audit cache
ejc3 Dec 25, 2025
efebcee
Remove auto-add of --setup flag from tests
ejc3 Dec 26, 2025
58bca12
Add detailed UFFD copy error logging
ejc3 Dec 26, 2025
bc9c760
Add filtered dmesg to CI artifacts
ejc3 Dec 26, 2025
4544e28
Fix UFFD error logging: use Debug format to show errno
ejc3 Dec 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
348 changes: 257 additions & 91 deletions .claude/CLAUDE.md

Large diffs are not rendered by default.

27 changes: 20 additions & 7 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,36 @@ retries = 0
[test-groups.stress-tests]
max-threads = 1

# Snapshot tests limited to 3 concurrent (each snapshot is ~5.6GB on disk)
[test-groups.snapshot-tests]
max-threads = 3

# VM tests run at full parallelism (num-cpus)
# Previously limited to 16 threads due to namespace holder process deaths,
# but root cause was rootless tests running under sudo. Now that privileged
# tests filter out rootless tests (-E '!test(/rootless/)'), full parallelism works.
[test-groups.vm-tests]
max-threads = "num-cpus"

[[profile.default.overrides]]
filter = "package(fcvm) & test(/stress_100/)"
test-group = "stress-tests"
slow-timeout = { period = "300s", terminate-after = 1 }
slow-timeout = { period = "600s", terminate-after = 1 }

# Snapshot tests: limited to 3 concurrent (each creates ~5.6GB snapshot on disk)
[[profile.default.overrides]]
filter = "package(fcvm) & (test(/snapshot/) | test(/clone/))"
test-group = "snapshot-tests"
slow-timeout = { period = "600s", terminate-after = 1 }

# VM tests get 10 minute timeout (non-snapshot tests)
[[profile.default.overrides]]
filter = "package(fcvm) & test(/test_/) & !test(/stress_100/) & !test(/pjdfstest_vm/) & !test(/snapshot/) & !test(/clone/)"
test-group = "vm-tests"
slow-timeout = { period = "600s", terminate-after = 1 }

# VM tests run with limited parallelism to avoid resource exhaustion
# In-VM pjdfstest needs 15 minutes (image import via FUSE over vsock is slow)
[[profile.default.overrides]]
filter = "package(fcvm) & test(/test_/) & !test(/stress_100/)"
filter = "package(fcvm) & test(/pjdfstest_vm/)"
test-group = "vm-tests"
slow-timeout = { period = "300s", terminate-after = 1 }
slow-timeout = { period = "900s", terminate-after = 1 }

# fuse-pipe tests can run with full parallelism
[[profile.default.overrides]]
Expand Down
160 changes: 123 additions & 37 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,23 @@ on:
push:
branches: [main]

# Cancel in-progress runs when a new revision is pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always
FUSE_BACKEND_RS: ${{ github.workspace }}/fuse-backend-rs
FUSER: ${{ github.workspace }}/fuser
CONTAINER_ARCH: x86_64

jobs:
container-rootless:
name: Container (rootless)
runs-on: ubuntu-latest
# Runner 1: Host (bare metal with KVM)
# Runs: test-unit → test-fast → test-root (sequential)
host:
name: Host
runs-on: buildjet-32vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -30,33 +37,80 @@ jobs:
repository: ejc3/fuser
ref: master
path: fuser
- name: make ci-container-rootless
working-directory: fcvm
run: make ci-container-rootless

container-sudo:
name: Container (sudo)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
path: fcvm
- uses: actions/checkout@v4
with:
repository: ejc3/fuse-backend-rs
ref: master
path: fuse-backend-rs
- uses: actions/checkout@v4
- name: Install Rust
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- uses: Swatinem/rust-cache@v2
with:
repository: ejc3/fuser
ref: master
path: fuser
- name: make ci-container-sudo
cache-provider: buildjet
workspaces: fcvm -> target
cache-on-failure: "true"
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y fuse3 libfuse3-dev libclang-dev clang musl-tools \
iproute2 iptables slirp4netns dnsmasq qemu-utils e2fsprogs parted \
podman skopeo busybox-static cpio zstd autoconf automake libtool
- name: Install Firecracker
run: |
curl -L -o /tmp/firecracker.tgz \
https://github.com/firecracker-microvm/firecracker/releases/download/v1.14.0/firecracker-v1.14.0-x86_64.tgz
sudo tar -xzf /tmp/firecracker.tgz -C /usr/local/bin --strip-components=1 \
release-v1.14.0-x86_64/firecracker-v1.14.0-x86_64 \
release-v1.14.0-x86_64/jailer-v1.14.0-x86_64
sudo mv /usr/local/bin/firecracker-v1.14.0-x86_64 /usr/local/bin/firecracker
sudo mv /usr/local/bin/jailer-v1.14.0-x86_64 /usr/local/bin/jailer
- name: Install cargo tools
# cargo-audit >= 0.22.0 required for CVSS 4.0 support
# Use --force to override any stale cached versions
run: cargo install cargo-nextest@0.9.115 cargo-audit@0.22.0 cargo-deny@0.18.9 --locked --force
- name: Setup KVM and networking
run: |
sudo chmod 666 /dev/kvm
sudo mkdir -p /var/run/netns
sudo iptables -P FORWARD ACCEPT
sudo iptables -t nat -A POSTROUTING -s 172.30.0.0/16 -o eth0 -j MASQUERADE || true
if [ ! -e /dev/userfaultfd ]; then
sudo mknod /dev/userfaultfd c 10 126
fi
sudo chmod 666 /dev/userfaultfd
sudo sysctl -w vm.unprivileged_userfaultfd=1
# Enable FUSE allow_other for tests
echo "user_allow_other" | sudo tee /etc/fuse.conf
- name: Create test log directory
run: mkdir -p /tmp/fcvm-test-logs
- name: test-unit
working-directory: fcvm
run: make ci-container-sudo
run: make test-unit
- name: setup-fcvm
working-directory: fcvm
run: make setup-fcvm
- name: test-fast
working-directory: fcvm
run: make test-fast
- name: test-root
working-directory: fcvm
run: make test-root
- name: Capture kernel logs
if: always()
run: |
# Filter dmesg for UFFD/memory/VM related messages only
sudo dmesg | grep -iE 'userfault|uffd|kvm|firecracker|oom|killed|segfault|page.fault' > /tmp/fcvm-test-logs/dmesg-filtered.log || true
- name: Upload test logs
if: always()
uses: actions/upload-artifact@v4
with:
name: test-logs-host
path: /tmp/fcvm-test-logs/
if-no-files-found: ignore
retention-days: 7

vm:
name: Host (sudo+rootless)
# Runner 2: Container (podman)
# Runs same tests as Host but inside a container
# Needs KVM for VM tests (container mounts /dev/kvm)
container:
name: Container
runs-on: buildjet-32vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v4
Expand All @@ -72,17 +126,49 @@ jobs:
repository: ejc3/fuser
ref: master
path: fuser
- name: Setup KVM and networking
- name: Setup KVM and rootless podman
run: |
sudo chmod 666 /dev/kvm
sudo mkdir -p /var/run/netns
sudo iptables -P FORWARD ACCEPT
sudo iptables -t nat -A POSTROUTING -s 172.30.0.0/16 -o eth0 -j MASQUERADE || true
if [ ! -e /dev/userfaultfd ]; then
sudo mknod /dev/userfaultfd c 10 126
fi
sudo chmod 666 /dev/userfaultfd
# Enable userfaultfd syscall for snapshot cloning
sudo sysctl -w vm.unprivileged_userfaultfd=1
- name: make container-test-vm
# Configure rootless podman to use cgroupfs (no systemd session on CI)
mkdir -p ~/.config/containers
printf '[engine]\ncgroup_manager = "cgroupfs"\nevents_logger = "file"\n' > ~/.config/containers/containers.conf
# Create cargo cache directory for container
mkdir -p ${{ github.workspace }}/cargo-cache/registry ${{ github.workspace }}/cargo-cache/target
- name: Cache container cargo
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/cargo-cache
key: container-cargo-${{ hashFiles('fcvm/Cargo.lock') }}
restore-keys: container-cargo-
- name: Create test log directory
run: mkdir -p /tmp/fcvm-test-logs
- name: container-test-unit
env:
CARGO_CACHE_DIR: ${{ github.workspace }}/cargo-cache
working-directory: fcvm
run: make container-test-unit
- name: container-setup-fcvm
env:
CARGO_CACHE_DIR: ${{ github.workspace }}/cargo-cache
working-directory: fcvm
run: make container-test-vm
run: make container-setup-fcvm
- name: container-test
env:
CARGO_CACHE_DIR: ${{ github.workspace }}/cargo-cache
working-directory: fcvm
run: make container-test
- name: Capture kernel logs
if: always()
run: |
# Filter dmesg for UFFD/memory/VM related messages only
sudo dmesg | grep -iE 'userfault|uffd|kvm|firecracker|oom|killed|segfault|page.fault' > /tmp/fcvm-test-logs/dmesg-filtered.log || true
- name: Upload test logs
if: always()
uses: actions/upload-artifact@v4
with:
name: test-logs-container
path: /tmp/fcvm-test-logs/
if-no-files-found: ignore
retention-days: 7
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ sync-test/
# Local settings (machine-specific)
*.local.*
*.local
cargo-home/
.local/
6 changes: 5 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,16 @@ Have an idea? [Open an issue](https://github.com/ejc3/fcvm/issues/new) describin
# Build everything
make build

# First-time setup (downloads kernel + creates rootfs, ~5-10 min)
make setup-btrfs
fcvm setup

# Run lints (must pass before PR)
make lint

# Run tests
make test # fuse-pipe tests
make test-vm # VM integration tests (requires KVM)
make test-root # VM tests (requires sudo + KVM)

# Format code
make fmt
Expand Down
24 changes: 2 additions & 22 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ members = [".", "fuse-pipe", "fc-agent"]
default-members = [".", "fuse-pipe", "fc-agent"]
# Exclude sync-test (used only for Makefile sync verification)
exclude = ["sync-test"]
# Resolver v2 makes --no-default-features work across all workspace members
resolver = "2"

[package]
name = "fcvm"
Expand All @@ -12,7 +14,6 @@ edition = "2021"

[dependencies]
anyhow = "1"
atty = "0.2"
clap = { version = "4", features = ["derive", "env"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down Expand Up @@ -42,11 +43,18 @@ fuse-pipe = { path = "fuse-pipe", default-features = false }
url = "2"
tokio-util = "0.7"
regex = "1.12.2"
fs2 = "0.4.3"

[features]
# Test category - only gate tests that require sudo
# Unprivileged tests run by default (no feature flag needed)
privileged-tests = [] # Tests requiring sudo (iptables, root podman storage)
# Default: all integration tests that work without sudo (rootless networking)
default = ["integration-fast", "integration-slow"]

# Test speed tiers (unit tests always run, no feature flag needed)
integration-fast = [] # Quick VM tests, < 30s each (sanity, signal, exec, port forward)
integration-slow = [] # Slow VM tests, > 30s each (clone, snapshot, fuse posix, egress)

# Privileged tests require sudo (bridged networking, pjdfstest, iptables)
privileged-tests = []

[dev-dependencies]
serial_test = "3"
Expand Down
Loading
Loading