Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 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
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
261 changes: 171 additions & 90 deletions .claude/CLAUDE.md

Large diffs are not rendered by default.

17 changes: 10 additions & 7 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,25 @@ retries = 0
max-threads = 1

# 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 }

# VM tests run with limited parallelism to avoid resource exhaustion
# VM tests get 10 minute timeout
[[profile.default.overrides]]
filter = "package(fcvm) & test(/test_/) & !test(/stress_100/)"
filter = "package(fcvm) & test(/test_/) & !test(/stress_100/) & !test(/pjdfstest_vm/)"
test-group = "vm-tests"
slow-timeout = { period = "300s", terminate-after = 1 }
slow-timeout = { period = "600s", terminate-after = 1 }

# In-VM pjdfstest needs 15 minutes (image import via FUSE over vsock is slow)
[[profile.default.overrides]]
filter = "package(fcvm) & test(/pjdfstest_vm/)"
test-group = "vm-tests"
slow-timeout = { period = "900s", terminate-after = 1 }

# fuse-pipe tests can run with full parallelism
[[profile.default.overrides]]
Expand Down
31 changes: 26 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ env:
jobs:
container-rootless:
name: Container (rootless)
runs-on: ubuntu-latest
runs-on: buildjet-32vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -36,7 +36,7 @@ jobs:

container-sudo:
name: Container (sudo)
runs-on: ubuntu-latest
runs-on: buildjet-32vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -56,7 +56,7 @@ jobs:
run: make ci-container-sudo

vm:
name: Host (sudo+rootless)
name: VM (bare metal)
runs-on: buildjet-32vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v4
Expand All @@ -72,6 +72,27 @@ jobs:
repository: ejc3/fuser
ref: master
path: fuser
- name: Install Rust
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- 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
- 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-nextest
run: cargo install cargo-nextest --locked
- name: Setup KVM and networking
run: |
sudo chmod 666 /dev/kvm
Expand All @@ -83,6 +104,6 @@ jobs:
fi
sudo chmod 666 /dev/userfaultfd
sudo sysctl -w vm.unprivileged_userfaultfd=1
- name: make container-test-vm
- name: make test-vm
working-directory: fcvm
run: make container-test-vm
run: make test-vm
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ sync-test/
# Local settings (machine-specific)
*.local.*
*.local
cargo-home/
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
120 changes: 23 additions & 97 deletions Containerfile
Original file line number Diff line number Diff line change
@@ -1,122 +1,48 @@
# fcvm test container
#
# Build context must include fuse-backend-rs and fuser alongside fcvm:
# cd ~/fcvm && podman build -t fcvm-test -f Containerfile \
# --build-context fuse-backend-rs=../fuse-backend-rs \
# --build-context fuser=../fuser .
#
# Test with: podman run --rm --privileged --device /dev/fuse fcvm-test

FROM docker.io/library/rust:1.83-bookworm

# Copy rust-toolchain.toml to read version from single source of truth
# Install Rust toolchain from rust-toolchain.toml
COPY rust-toolchain.toml /tmp/rust-toolchain.toml

# Install toolchain version from rust-toolchain.toml (avoids version drift)
# Edition 2024 is stable since Rust 1.85
# Also add musl targets for statically linked fc-agent (portable across glibc versions)
RUN RUST_VERSION=$(grep 'channel' /tmp/rust-toolchain.toml | cut -d'"' -f2) && \
rustup toolchain install $RUST_VERSION && \
rustup default $RUST_VERSION && \
rustup component add rustfmt clippy && \
rustup target add aarch64-unknown-linux-musl x86_64-unknown-linux-musl

# Install cargo-nextest for better test parallelism and output
RUN cargo install cargo-nextest --locked
# Install cargo tools
RUN cargo install cargo-nextest cargo-audit cargo-deny --locked

# Install system dependencies
RUN apt-get update && apt-get install -y \
# FUSE support
fuse3 \
libfuse3-dev \
# pjdfstest build deps
autoconf \
automake \
libtool \
# pjdfstest runtime deps
perl \
# Build deps for bindgen (userfaultfd-sys)
libclang-dev \
clang \
# musl libc for statically linked fc-agent (portable across glibc versions)
musl-tools \
# fcvm VM test dependencies
iproute2 \
iptables \
slirp4netns \
dnsmasq \
qemu-utils \
e2fsprogs \
parted \
# Container runtime for localhost image tests
podman \
skopeo \
# Utilities
git \
curl \
sudo \
procps \
# Required for initrd creation (must be statically linked for kernel boot)
busybox-static \
cpio \
# Clean up
fuse3 libfuse3-dev autoconf automake libtool perl libclang-dev clang \
musl-tools iproute2 iptables slirp4netns dnsmasq qemu-utils e2fsprogs \
parted podman skopeo git curl sudo procps zstd busybox-static cpio uidmap \
&& rm -rf /var/lib/apt/lists/*

# Download and install Firecracker (architecture-aware)
# v1.14.0 adds network_overrides support for snapshot cloning
# Install Firecracker
ARG ARCH=aarch64
RUN curl -L -o /tmp/firecracker.tgz \
RUN curl -fsSL -o /tmp/fc.tgz \
https://github.com/firecracker-microvm/firecracker/releases/download/v1.14.0/firecracker-v1.14.0-${ARCH}.tgz \
&& tar --no-same-owner -xzf /tmp/firecracker.tgz -C /tmp \
&& tar --no-same-owner -xzf /tmp/fc.tgz -C /tmp \
&& mv /tmp/release-v1.14.0-${ARCH}/firecracker-v1.14.0-${ARCH} /usr/local/bin/firecracker \
&& chmod +x /usr/local/bin/firecracker \
&& rm -rf /tmp/firecracker.tgz /tmp/release-v1.14.0-${ARCH}

# Build and install pjdfstest (tests expect it at /tmp/pjdfstest-check/)
RUN git clone --depth 1 https://github.com/pjd/pjdfstest /tmp/pjdfstest-check \
&& cd /tmp/pjdfstest-check \
&& autoreconf -ifs \
&& ./configure \
&& make
&& rm -rf /tmp/fc.tgz /tmp/release-v1.14.0-${ARCH}

# Create non-root test user with access to fuse group
RUN groupadd -f fuse \
# Setup testuser with sudo and namespace support
RUN echo "user_allow_other" >> /etc/fuse.conf \
&& groupadd -f fuse && groupadd -f kvm \
&& useradd -m -s /bin/bash testuser \
&& usermod -aG fuse testuser

# Rust tools are installed system-wide at /usr/local/cargo (owned by root)
# Symlink to /usr/local/bin so sudo can find them (sudo uses secure_path)
RUN ln -s /usr/local/cargo/bin/cargo /usr/local/bin/cargo \
&& ln -s /usr/local/cargo/bin/rustc /usr/local/bin/rustc \
&& ln -s /usr/local/cargo/bin/cargo-nextest /usr/local/bin/cargo-nextest

# Allow testuser to sudo without password (like host dev setup)
RUN echo "testuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# Configure subordinate UIDs/GIDs for rootless user namespaces
# testuser (UID 1000) gets subordinate range 100000-165535 (65536 IDs)
# This enables `unshare --user --map-auto` without root
RUN echo "testuser:100000:65536" >> /etc/subuid \
&& usermod -aG fuse,kvm testuser \
&& echo "testuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \
&& echo "testuser:100000:65536" >> /etc/subuid \
&& echo "testuser:100000:65536" >> /etc/subgid

# Install uidmap package for newuidmap/newgidmap setuid helpers
# These are required for --map-auto to work
RUN apt-get update && apt-get install -y uidmap && rm -rf /var/lib/apt/lists/*

# Create workspace structure matching local paths
# Source code is mounted at runtime, not copied - ensures code is always fresh
WORKDIR /workspace

# Create directories that will be mount points
RUN mkdir -p /workspace/fcvm /workspace/fuse-backend-rs /workspace/fuser

# Make workspace owned by testuser for non-root tests
RUN chown -R testuser:testuser /workspace
# Symlink cargo tools to /usr/local/bin for sudo
RUN for bin in cargo rustc rustfmt cargo-clippy clippy-driver cargo-nextest cargo-audit cargo-deny; do \
ln -s /usr/local/cargo/bin/$bin /usr/local/bin/$bin 2>/dev/null || true; done

# Setup workspace
WORKDIR /workspace/fcvm
RUN mkdir -p /workspace/fcvm /workspace/fuse-backend-rs /workspace/fuser \
&& chown -R testuser:testuser /workspace

# Switch to testuser - tests run as normal user with sudo like on host
USER testuser

# Default command runs all fuse-pipe tests
CMD ["cargo", "nextest", "run", "--release", "-p", "fuse-pipe"]
CMD ["make", "test-unit"]
Loading
Loading