Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/build
/target
/nfstest/scripts/__pycache__/
/nfstest/scripts/__pycache__/
# Compiled NFS-enabled client kernel (built on demand via `make nfstest-kernel`)
/nfstest/kernel/out/
.gstack/
32 changes: 28 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,36 @@ Arctic Wolf is a Rust-based NFSv3 server implementing RFC 1813 (NFSv3), RFC 5531
**Important:** Always use `make` commands for building and testing. Do not run `cargo` or `earthly` directly.

```bash
make build # Build in container
make test # Run unit tests
make lint # Run clippy + rustfmt
make nfstest # Full integration tests with VM
make build # Build in container (Earthly)
make test # Run unit tests (Earthly)
make lint # Run clippy + rustfmt (Earthly)
make nfstest # Full NFS integration test on Apple `container`
```

### Integration test (Apple `container`)

`make nfstest` runs the end-to-end NFSv3 test entirely on Apple's `container`
runtime — no Docker, Earthly, or QEMU. It boots the Arctic Wolf server and an
`nfstest_posix` client as two containers on the default `192.168.64.0/24`
bridge; the client mounts the server's `/data` export and runs the upstream
POSIX read/write suite. Build inputs live under `nfstest/`:

- `nfstest/server/Dockerfile` — server image (`container build`).
- `nfstest/client/Dockerfile` — nfstest client image.
- `nfstest/kernel/` — a Linux kernel `Image` rebuilt from Apple's own kernel
config with `CONFIG_NFS_FS=y` (the default Apple kernel has no NFS client).
`make nfstest-kernel` builds it once and caches it in `nfstest/kernel/out/`.
- `nfstest/scripts/nfstest.py` — orchestration (build / start-server /
run-test / stop) driven via the `container` CLI.

Override the POSIX test cases with `make nfstest TESTCASE=read,write`.

## Git Conventions

Every commit must be signed off (Developer Certificate of Origin). Always
create commits with `git commit -s` so a `Signed-off-by:` trailer is added.
When amending, use `git commit --amend -s`.

## Architecture

### Layer Structure
Expand Down
43 changes: 4 additions & 39 deletions Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -81,42 +81,7 @@ image:
ENTRYPOINT ["./target/release/arcticwolf"]
SAVE IMAGE ${IMAGE_REPO}/arcticwolf:${IMAGE_TAG}

# earthly +server-docker
# Build Docker image using the same build environment
server-docker:
ARG IMAGE_NAME=arcticwolf
ARG IMAGE_TAG=latest
FROM +common
RUN cargo build
ENV RUST_LOG=debug
EXPOSE 111 2049
ENTRYPOINT ["./target/debug/arcticwolf"]
SAVE IMAGE ${IMAGE_NAME}:${IMAGE_TAG}

# earthly +client-vm
# Build Alpine VM image for integration testing
client-vm:
ARG VM_OUTPUT_DIR=build/nfstest/vm
ARG VM_IMAGE_NAME=vm.qcow2
ARG CIDATA_NAME=cidata.iso
FROM alpine:3.19
RUN apk add --no-cache qemu-img cdrkit curl

WORKDIR /build

# Download Alpine nocloud image (x86_64 with cloud-init)
RUN curl -L -o vm.qcow2 \
https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/cloud/nocloud_alpine-3.19.0-x86_64-bios-cloudinit-r0.qcow2

# Resize image to have more space
RUN qemu-img resize vm.qcow2 2G

# Create cloud-init ISO (NoCloud datasource)
RUN mkdir -p cidata
COPY nfstest/vm/user-data ./cidata/user-data
RUN echo "instance-id: nfstest-vm" > cidata/meta-data
RUN genisoimage -output cidata.iso -volid cidata -joliet -rock cidata/

# Export VM artifacts to local build directory
SAVE ARTIFACT vm.qcow2 AS LOCAL ${VM_OUTPUT_DIR}/${VM_IMAGE_NAME}
SAVE ARTIFACT cidata.iso AS LOCAL ${VM_OUTPUT_DIR}/${CIDATA_NAME}
# The local NFS integration test no longer uses Earthly. It runs entirely on
# Apple `container` (server + nfstest client + custom NFS-enabled kernel) and
# is driven by `make nfstest` -> nfstest/scripts/nfstest.py. See
# nfstest/server, nfstest/client, and nfstest/kernel for the build inputs.
65 changes: 36 additions & 29 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
EARTHLY ?= earthly

# Docker image configuration
# Docker image configuration (used by the Earthly `image` target only)
IMAGE_NAME ?= arcticwolf
IMAGE_REPO ?= freezevicente
IMAGE_TAG ?= latest

# VM configuration
VM_OUTPUT_DIR ?= build/nfstest/vm
VM_IMAGE_NAME ?= vm.qcow2
CIDATA_NAME ?= cidata.iso
# Integration test orchestration (Apple `container`)
NFSTEST ?= ./nfstest/scripts/nfstest.py

# Test configuration
TESTCASE ?= read,write

# Default target
.DEFAULT_GOAL := help

.PHONY: help build test lint lockfile start-test-env nfstest stop-test-env clean
.PHONY: help build test lint image lockfile \
nfstest nfstest-build nfstest-kernel nfstest-up nfstest-down clean

# Show available targets and their descriptions
help:
@echo "Available targets:"
@echo " build - Build release binary"
@echo " test - Run unit tests"
@echo " lint - Run clippy and rustfmt checks"
@echo " build - Build release binary (Earthly)"
@echo " test - Run unit tests (Earthly)"
@echo " lint - Run clippy and rustfmt checks (Earthly)"
@echo " lockfile - Regenerate Cargo.lock from Cargo.toml"
@echo " start-test-env - Build and start both server and client VM"
@echo " nfstest - Run NFS tests (TESTCASE=read,write)"
@echo " stop-test-env - Stop both server and VM"
@echo " clean - Stop all and remove build artifacts"
@echo " nfstest - Run NFS integration test on Apple containers"
@echo " nfstest-build - Build server + client images"
@echo " nfstest-kernel - Build the NFS-enabled client kernel"
@echo " nfstest-up - Start the server container"
@echo " nfstest-down - Stop and remove test containers"
@echo " clean - Stop containers and remove build artifacts"
@echo ""
@echo "Examples:"
@echo " make nfstest # Run default tests (read,write)"
@echo " make nfstest TESTCASE=read # Run only read test"
@echo " make nfstest # Run default tests (read,write)"
@echo " make nfstest TESTCASE=read # Run only read test"
@echo " make nfstest TESTCASE=read,write # Run read and write tests"

# Build release binary
Expand All @@ -51,24 +52,30 @@ lint:
lockfile:
$(EARTHLY) +lockfile

# Build Docker image
# Build published Docker image (Earthly)
image:
${EARTHLY} +image --IMAGE_REPO=$(IMAGE_REPO) --IMAGE_TAG=$(IMAGE_TAG)

# Build and start both server and VM
start-test-env:
@$(EARTHLY) +server-docker --IMAGE_NAME=$(IMAGE_NAME) --IMAGE_TAG=$(IMAGE_TAG)
@$(EARTHLY) +client-vm --VM_OUTPUT_DIR=$(VM_OUTPUT_DIR) --VM_IMAGE_NAME=$(VM_IMAGE_NAME) --CIDATA_NAME=$(CIDATA_NAME)
@./nfstest/scripts/nfstest.py start-env --image-name=$(IMAGE_NAME) --image-tag=$(IMAGE_TAG) --vm-dir=$(VM_OUTPUT_DIR) --vm-image=$(VM_IMAGE_NAME) --cidata=$(CIDATA_NAME)
# Build the NFS-enabled kernel for the client container (cached after first run)
nfstest-kernel:
@$(NFSTEST) build-kernel

# Run NFS tests (builds if needed)
nfstest: stop-test-env start-test-env
@./nfstest/scripts/nfstest.py test --image-name=$(IMAGE_NAME) --image-tag=$(IMAGE_TAG) --vm-dir=$(VM_OUTPUT_DIR) --vm-image=$(VM_IMAGE_NAME) --cidata=$(CIDATA_NAME) --testcase=$(TESTCASE)
# Build server + client images
nfstest-build:
@$(NFSTEST) build-images

# Stop both server and VM
stop-test-env:
@./nfstest/scripts/nfstest.py stop-env
# Start the server container
nfstest-up:
@$(NFSTEST) start-server

# Clean build artifacts and stop running test containers/VMs
clean: stop-test-env
# Stop and remove test containers
nfstest-down:
@$(NFSTEST) stop

# Run the full integration test on Apple containers
nfstest: nfstest-kernel nfstest-build
@$(NFSTEST) test --testcase=$(TESTCASE)

# Clean build artifacts and stop running test containers
clean: nfstest-down
rm -rf target build
35 changes: 35 additions & 0 deletions nfstest/client/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# NFS client image for the Apple-container integration test.
#
# Runs upstream nfstest's `nfstest_posix` against the Arctic Wolf server.
# Built natively for arm64 so it needs no emulation. The in-kernel NFS
# client is provided at run time via `container run --kernel` (Apple's
# default kernel has CONFIG_NFS_FS disabled — see nfstest/kernel/).
#
# container build -t arcticwolf-nfstest:client -f nfstest/client/Dockerfile nfstest/client

FROM debian:bookworm-slim

# nfs-common: mount.nfs + rpc helpers. python3: nfstest is a Python suite.
# procps/iproute2: nfstest_posix probes the environment during setup.
RUN apt-get update && apt-get install -y --no-install-recommends \
nfs-common \
netbase \
python3 \
python3-pip \
git \
ca-certificates \
iproute2 \
procps \
tcpdump \
sudo \
&& rm -rf /var/lib/apt/lists/*

# Install nfstest from its git repository (same source the QEMU VM used).
RUN git clone --depth 1 git://git.linux-nfs.org/projects/mora/nfstest.git /opt/nfstest

ENV PYTHONPATH=/opt/nfstest
ENV PATH=/opt/nfstest/test:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

RUN mkdir -p /mnt/nfstest

ENTRYPOINT ["/bin/sh", "-c"]
28 changes: 28 additions & 0 deletions nfstest/config/arcticwolf.test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Arctic Wolf configuration used by the Apple-container integration test.
#
# Mirrors arcticwolf.example.toml but pins logging to debug and uses export
# paths that the server image (nfstest/server/Dockerfile) creates at build
# time. The POSIX suite drives /data (rw); /backup is mounted read_only so
# the read-only enforcement path is exercised against a real Linux client.

[server]
bind_address = "0.0.0.0"
nfs_port = 2049
mount_port = 0

[[exports]]
name = "/data"
uid = 1
read_only = false
backend = "local"
path = "/srv/nfs/data"

[[exports]]
name = "/backup"
uid = 2
read_only = true
backend = "local"
path = "/srv/nfs/backup"

[logging]
level = "debug"
57 changes: 57 additions & 0 deletions nfstest/kernel/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Builds a Linux kernel Image (arm64) with the in-kernel NFS client enabled.
#
# Apple's `container` runtime ships a minimal kernel that has
# `CONFIG_NFS_FS` disabled, so a client container cannot `mount -t nfs`.
# We rebuild the *same* kernel version from Apple's own extracted config
# (apple-container.config) with the NFS client compiled in (=y, since the
# config has CONFIG_MODULES disabled).
#
# The resulting arch/arm64/boot/Image is what `container run --kernel`
# expects (the default kernel is itself an arm64 boot Image).

FROM debian:bookworm AS build

ARG KERNEL_VERSION=6.18.15

RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
bc bison flex \
libssl-dev libelf-dev \
curl ca-certificates xz-utils \
cpio kmod \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /build

RUN curl -fL -o linux.tar.xz \
"https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${KERNEL_VERSION}.tar.xz" \
&& tar -xf linux.tar.xz \
&& rm linux.tar.xz

WORKDIR /build/linux-${KERNEL_VERSION}

# Start from Apple's container kernel config so the virtio/vsock plumbing
# matches what the Virtualization.framework guest expects.
COPY apple-container.config .config

# Enable the in-kernel NFS v3 client. CONFIG_MODULES is off in the base
# config, so everything must be built in (=y). olddefconfig then resolves
# the selected dependencies (SUNRPC, LOCKD, NFS_COMMON, ...).
RUN scripts/config --file .config \
-e NETWORK_FILESYSTEMS \
-e SUNRPC \
-e NFS_FS \
-e NFS_V2 \
-e NFS_V3 \
-e NFS_V3_ACL \
-d NFS_V4 \
&& make olddefconfig \
&& grep -E "CONFIG_NFS_FS|CONFIG_NFS_V3|CONFIG_SUNRPC" .config

RUN make -j"$(nproc)" Image \
&& cp arch/arm64/boot/Image /build/Image \
&& ls -la /build/Image

# Minimal final stage so the Image can be copied out cheaply.
FROM scratch AS artifact
COPY --from=build /build/Image /Image
Loading
Loading