From 79101e748ac080a52c958b43b79547bc9e1dabc2 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Sat, 2 Jul 2022 19:49:49 +0100 Subject: [PATCH] test: Replace RTF with Rust This commit replaces the existing RTF test runner with a simple rust binary package called - integration-test. integration-test depends on integration-ebpf, which contains test eBPF code written in Rust and C. `cargo xtask build-integration-test-ebpf` can be used to build this code and supress rust-analyzer warnings. It does require `bpf-linker`, but that is highly likely to be available to developers of Aya. It also requires a checkout of `libbpf` to extract headers like bpf-helpers.h. Since everything is compiled into a single binary, it can be run be run locally using `cargo xtask integration-test` or remotely using `./run.sh` which re-uses the bash script from the old test framework to spawn a VM in which to run the tests. Signed-off-by: Dave Tucker --- .github/workflows/build-aya.yml | 33 +++- .github/workflows/images.yml | 53 ------ .github/workflows/lint.yml | 12 +- .vim/coc-settings.json | 3 +- .vscode/settings.json | 3 +- Cargo.toml | 3 +- bpf/rustfmt.toml | 1 + images/Dockerfile.rtf | 38 ---- test/.gitignore | 4 +- test/README.md | 58 ++++--- test/cases/000_smoke/000_xdp/pass.rs | 19 -- test/cases/000_smoke/000_xdp/test.sh | 29 ---- test/cases/000_smoke/010_ext/ext.rs | 24 --- test/cases/000_smoke/010_ext/test.sh | 33 ---- test/cases/000_smoke/group.sh | 36 ---- test/cases/010_load/000_name/name_test.rs | 20 --- test/cases/010_load/000_name/test.sh | 32 ---- .../010_load/010_multiple_maps/multimap.rs | 33 ---- test/cases/010_load/010_multiple_maps/test.sh | 29 ---- test/cases/010_load/030_unload/test.rs | 57 ------ test/cases/010_load/030_unload/test.sh | 27 --- test/cases/010_load/group.sh | 36 ---- test/cases/020_elf/000_maps/map_test.o | Bin 107384 -> 0 bytes test/cases/020_elf/000_maps/test.sh | 25 --- test/cases/020_elf/group.sh | 36 ---- test/cases/_lib/compile-ebpf.ers | 85 --------- test/cases/group.sh | 36 ---- test/integration-ebpf/.cargo/config.toml | 6 + test/integration-ebpf/Cargo.toml | 36 ++++ test/integration-ebpf/rust-toolchain.toml | 2 + test/integration-ebpf/rustfmt.toml | 1 + .../src/bpf}/ext.bpf.c | 3 +- .../src/bpf}/main.bpf.c | 3 +- .../src/bpf}/multimap.bpf.c | 2 +- .../src/map_test.rs} | 7 +- .../src/name_test.rs} | 17 +- .../src/pass.rs} | 15 +- .../src/test.rs} | 5 - test/integration-test-macros/Cargo.toml | 12 ++ test/integration-test-macros/src/lib.rs | 19 ++ test/integration-test/Cargo.toml | 17 ++ test/integration-test/src/main.rs | 27 +++ test/integration-test/src/tests/elf.rs | 27 +++ test/integration-test/src/tests/load.rs | 75 ++++++++ test/integration-test/src/tests/mod.rs | 37 ++++ test/integration-test/src/tests/smoke.rs | 45 +++++ test/{cases/_lib/lib.sh => run.sh} | 101 ++--------- xtask/Cargo.toml | 2 + xtask/src/build_ebpf.rs | 162 ++++++++++++++++++ xtask/src/build_test.rs | 29 ++++ xtask/src/docs/mod.rs | 8 +- xtask/src/main.rs | 10 ++ xtask/src/run.rs | 72 ++++++++ xtask/src/utils.rs | 17 ++ 54 files changed, 693 insertions(+), 829 deletions(-) delete mode 100644 .github/workflows/images.yml create mode 120000 bpf/rustfmt.toml delete mode 100644 images/Dockerfile.rtf delete mode 100755 test/cases/000_smoke/000_xdp/pass.rs delete mode 100755 test/cases/000_smoke/000_xdp/test.sh delete mode 100755 test/cases/000_smoke/010_ext/ext.rs delete mode 100755 test/cases/000_smoke/010_ext/test.sh delete mode 100755 test/cases/000_smoke/group.sh delete mode 100755 test/cases/010_load/000_name/name_test.rs delete mode 100755 test/cases/010_load/000_name/test.sh delete mode 100755 test/cases/010_load/010_multiple_maps/multimap.rs delete mode 100755 test/cases/010_load/010_multiple_maps/test.sh delete mode 100755 test/cases/010_load/030_unload/test.rs delete mode 100755 test/cases/010_load/030_unload/test.sh delete mode 100755 test/cases/010_load/group.sh delete mode 100644 test/cases/020_elf/000_maps/map_test.o delete mode 100755 test/cases/020_elf/000_maps/test.sh delete mode 100644 test/cases/020_elf/group.sh delete mode 100644 test/cases/_lib/compile-ebpf.ers delete mode 100755 test/cases/group.sh create mode 100644 test/integration-ebpf/.cargo/config.toml create mode 100644 test/integration-ebpf/Cargo.toml create mode 100644 test/integration-ebpf/rust-toolchain.toml create mode 120000 test/integration-ebpf/rustfmt.toml rename test/{cases/000_smoke/010_ext => integration-ebpf/src/bpf}/ext.bpf.c (65%) rename test/{cases/000_smoke/010_ext => integration-ebpf/src/bpf}/main.bpf.c (65%) rename test/{cases/010_load/010_multiple_maps => integration-ebpf/src/bpf}/multimap.bpf.c (96%) rename test/{cases/020_elf/000_maps/map_test.ebpf.rs => integration-ebpf/src/map_test.rs} (87%) rename test/{cases/010_load/000_name/name_test.ebpf.rs => integration-ebpf/src/name_test.rs} (57%) rename test/{cases/000_smoke/000_xdp/pass.ebpf.rs => integration-ebpf/src/pass.rs} (65%) rename test/{cases/010_load/030_unload/test.ebpf.rs => integration-ebpf/src/test.rs} (84%) create mode 100644 test/integration-test-macros/Cargo.toml create mode 100644 test/integration-test-macros/src/lib.rs create mode 100644 test/integration-test/Cargo.toml create mode 100644 test/integration-test/src/main.rs create mode 100644 test/integration-test/src/tests/elf.rs create mode 100644 test/integration-test/src/tests/load.rs create mode 100644 test/integration-test/src/tests/mod.rs create mode 100644 test/integration-test/src/tests/smoke.rs rename test/{cases/_lib/lib.sh => run.sh} (63%) mode change 100644 => 100755 create mode 100644 xtask/src/build_ebpf.rs create mode 100644 xtask/src/build_test.rs create mode 100644 xtask/src/run.rs create mode 100644 xtask/src/utils.rs diff --git a/.github/workflows/build-aya.yml b/.github/workflows/build-aya.yml index 90a3b6ff..9d523875 100644 --- a/.github/workflows/build-aya.yml +++ b/.github/workflows/build-aya.yml @@ -38,14 +38,35 @@ jobs: test: runs-on: ubuntu-20.04 needs: build - container: - image: ghcr.io/aya-rs/aya-test-rtf:main steps: - uses: actions/checkout@v2 + - uses: actions/checkout@v2 + with: + repository: libbpf/libbpf + path: libbpf + + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + components: rustfmt, clippy, rust-src + target: x86_64-unknown-linux-musl + override: true + + - uses: Swatinem/rust-cache@v1 + + - name: Install Pre-requisites + run: | + sudo apt-get -qy install linux-tools-common qemu-system-x86 cloud-image-utils openssh-client libelf-dev gcc-multilib + cargo install bpf-linker + + + - name: Lint integration tests + run: | + cargo xtask build-integration-test-ebpf --libbpf-dir ./libbpf + cargo clippy -p integration-test -- --deny warnings + cargo clippy -p integration-test-macros -- --deny warnings - - name: Run regression tests + - name: Run integration tests run: | - ln -s /root/.rustup ${HOME}/.rustup - cd test - rtf -vvv run + (cd test && ./run.sh ../libbpf) diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml deleted file mode 100644 index d13d3aae..00000000 --- a/.github/workflows/images.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: aya-test-image - -on: - schedule: - - cron: "42 2 * * 0" - push: - branches: - - 'main' - paths: - - 'images/**' - - '.github/workflows/images.yml' - pull_request: - branches: - - 'main' - paths: - - 'images/**' - - '.github/workflows/images.yml' - -env: - REGISTRY: ghcr.io - IMAGE_NAME: aya-rs/aya-test-rtf - -jobs: - rtf: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - uses: actions/checkout@v2 - - - name: Log in to the Container registry - uses: docker/login-action@v1 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v3 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Build and push Docker image - uses: docker/build-push-action@v2 - with: - context: images - file: images/Dockerfile.rtf - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 59b3d16f..ffb10c76 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,24 +24,22 @@ jobs: with: profile: minimal toolchain: nightly - components: rustfmt, clippy, miri + components: rustfmt, clippy, miri, rust-src override: true - name: Check formatting run: | cargo fmt --all -- --check - pushd bpf - cargo fmt --all -- --check - popd + (cd bpf && cargo fmt --all -- --check) + (cd test/integration-ebpf && cargo fmt --all -- --check) - name: Run clippy run: | cargo clippy -p aya -- --deny warnings cargo clippy -p aya-gen -- --deny warnings cargo clippy -p xtask -- --deny warnings - pushd bpf - cargo clippy -p aya-bpf -- --deny warnings - popd + (cd bpf && cargo clippy -p aya-bpf -- --deny warnings) + (cd test/integration-ebpf && cargo clippy -- --deny warnings) - name: Run miri env: diff --git a/.vim/coc-settings.json b/.vim/coc-settings.json index 60fe4ee3..4b796ccb 100644 --- a/.vim/coc-settings.json +++ b/.vim/coc-settings.json @@ -1,3 +1,4 @@ { - "rust-analyzer.linkedProjects": ["Cargo.toml", "bpf/Cargo.toml"] + "rust-analyzer.linkedProjects": ["Cargo.toml", "bpf/Cargo.toml", "test/integration-ebpf/Cargo.toml"], + "rust-analyzer.checkOnSave.allTargets": false } diff --git a/.vscode/settings.json b/.vscode/settings.json index 60fe4ee3..4b796ccb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "rust-analyzer.linkedProjects": ["Cargo.toml", "bpf/Cargo.toml"] + "rust-analyzer.linkedProjects": ["Cargo.toml", "bpf/Cargo.toml", "test/integration-ebpf/Cargo.toml"], + "rust-analyzer.checkOnSave.allTargets": false } diff --git a/Cargo.toml b/Cargo.toml index 0eed0f81..c5a9a2e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] -members = ["aya", "aya-gen", "xtask"] +members = ["aya", "aya-gen", "test/integration-test", "test/integration-test-macros", "xtask"] +default-members = ["aya", "aya-gen"] diff --git a/bpf/rustfmt.toml b/bpf/rustfmt.toml new file mode 120000 index 00000000..39f97b04 --- /dev/null +++ b/bpf/rustfmt.toml @@ -0,0 +1 @@ +../rustfmt.toml \ No newline at end of file diff --git a/images/Dockerfile.rtf b/images/Dockerfile.rtf deleted file mode 100644 index 72f28cf1..00000000 --- a/images/Dockerfile.rtf +++ /dev/null @@ -1,38 +0,0 @@ -FROM fedora:35 - -# Rust Nightly -RUN curl https://sh.rustup.rs -sSf | sh -s -- \ - --default-toolchain nightly \ - --component rustfmt \ - --component clippy \ - --component rust-src \ - --target x86_64-unknown-linux-musl \ - -y - -ENV PATH "/root/.cargo/bin:$PATH" - -# Pre-requisites -RUN dnf install \ - --setopt=install_weak_deps=False --best -qy \ - golang \ - qemu-system-x86 \ - cloud-utils \ - genisoimage \ - libbpf-devel \ - clang \ - openssl-devel \ - musl-libc \ - git && dnf clean all \ - && rm -rf /var/cache/yum - -RUN cargo install \ - bpf-linker \ - rust-script \ - sccache - -RUN go install github.com/linuxkit/rtf@latest -ENV PATH "/root/go/bin:$PATH" -ENV RUSTC_WRAPPER "sccache" - -ENTRYPOINT ["rtf"] -CMD ["-vvv", "run"] diff --git a/test/.gitignore b/test/.gitignore index 19caa99c..d36977dc 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,3 +1 @@ -_results -_tmp -_images \ No newline at end of file +.tmp diff --git a/test/README.md b/test/README.md index 164f5b56..81430af2 100644 --- a/test/README.md +++ b/test/README.md @@ -1,48 +1,52 @@ -Aya Regression Tests -==================== +Aya Integration Tests +===================== -The aya regression test suite is a set of tests to ensure that +The aya integration test suite is a set of tests to ensure that common usage behaviours work on real Linux distros ## Prerequisites -This assumes you have a working Rust and Go toolchain on the host machine +### Linux -1. `rustup target add x86_64-unknown-linux-musl` -1. Install [`rtf`](https://github.com/linuxkit/rtf): `go install github.com/linuxkit/rtf@latest` -1. Install rust-script: `cargo install rust-script` -1. Install `qemu` and `cloud-init-utils` package - or any package that provides `cloud-localds` +To run locally all you need is: -It is not required, but the tests run significantly faster if you use `sccache` +1. Rust nightly +1. A checkout of `libbpf` +1. `cargo install bpf-linker` +1. `bpftool` -You may also use the docker image to run the tests: +### Other OSs -``` -docker run -it --rm --device /dev/kvm -v/home/dave/dev/aya-rs/aya:/src -w /src/test ghcr.io/aya-rs/aya-test-rtf:main -``` +1. A POSIX shell +1. A checkout of `libbpf` +1. `rustup target add x86_64-unknown-linux-musl` +1. `cargo install bpf-linker` +1. Install `qemu` and `cloud-init-utils` package - or any package that provides `cloud-localds` ## Usage -To read more about how to use `rtf`, see the [documentation](https://github.com/linuxkit/rtf/blob/master/docs/USER_GUIDE.md) +From the root of this repository: -### Run the tests with verbose output +### Native ``` -rtf -vvv run +cargo xtask integration-test --libbpf-dir /path/to/libbpf ``` -### Run the tests using an older kernel + +### Virtualized + ``` -AYA_TEST_IMAGE=centos8 rtf -vvv run +./test/run.sh /path/to/libbpf ``` - ### Writing a test -Tests should follow this pattern: - -- The eBPF code should be in a file named `${NAME}.ebpf.rs` -- The userspace code should be in a file named `${NAME}.rs` -- The userspace program should make assertions and exit with a non-zero return code to signal failure -- VM start and stop is handled by the framework -- Any files copied to the VM should be cleaned up afterwards +Tests should follow these guidelines: -See `./cases` for examples \ No newline at end of file +- Rust eBPF code should live in `integration-ebpf/${NAME}.rs` and included in `integration-ebpf/Cargo.toml` +- C eBPF code should live in `integration-test/src/bpf/${NAME}.bpf.c`. It's automatically compiled and made available as `${OUT_DIR}/${NAME}.bpf.o`. +- Any bytecode should be included in the integration test binary using `include_bytes_aligned!` +- Tests should be added to `integration-test/src/test` +- You may add a new module, or use an existing one +- Integration tests must use the `#[integration_test]` macro to be included in the build +- Test functions should return `anyhow::Result<()>` since this allows the use of `?` to return errors. +- You may either `panic!` when an assertion fails or `bail!`. The former is preferred since the stack trace will point directly to the failed line. diff --git a/test/cases/000_smoke/000_xdp/pass.rs b/test/cases/000_smoke/000_xdp/pass.rs deleted file mode 100755 index f5ca55c8..00000000 --- a/test/cases/000_smoke/000_xdp/pass.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! ```cargo -//! [dependencies] -//! aya = { path = "../../../../aya" } -//! ``` - -use aya::{ - Bpf, - programs::{Xdp, XdpFlags}, -}; -use std::convert::TryInto; - -fn main() { - println!("Loading XDP program"); - let mut bpf = Bpf::load_file("pass.o").unwrap(); - let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); - dispatcher.load().unwrap(); - dispatcher.attach("eth0", XdpFlags::default()).unwrap(); - println!("Success..."); -} diff --git a/test/cases/000_smoke/000_xdp/test.sh b/test/cases/000_smoke/000_xdp/test.sh deleted file mode 100755 index eba23581..00000000 --- a/test/cases/000_smoke/000_xdp/test.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh -# SUMMARY: Check that a simple XDP program an be loaded -# LABELS: - -set -e - -# Source libraries. Uncomment if needed/defined -#. "${RT_LIB}" -. "${RT_PROJECT_ROOT}/_lib/lib.sh" - -NAME=pass - -clean_up() { - rm -rf ${NAME}.o ${NAME} - exec_vm rm -f ${NAME} ${NAME}.o -} - -trap clean_up EXIT - -# Test code goes here -compile_ebpf "$(pwd)/${NAME}.ebpf.rs" -compile_user "$(pwd)/${NAME}.rs" - -scp_vm ${NAME}.o -scp_vm ${NAME} - -exec_vm sudo ./${NAME} - -exit 0 \ No newline at end of file diff --git a/test/cases/000_smoke/010_ext/ext.rs b/test/cases/000_smoke/010_ext/ext.rs deleted file mode 100755 index 909a848b..00000000 --- a/test/cases/000_smoke/010_ext/ext.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! ```cargo -//! [dependencies] -//! aya = { path = "../../../../aya" } -//! ``` - -use aya::{ - Bpf, BpfLoader, - programs::{Extension, ProgramFd, Xdp, XdpFlags}, -}; -use std::convert::TryInto; - -fn main() { - println!("Loading Root XDP program"); - let mut bpf = Bpf::load_file("main.o").unwrap(); - let pass: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); - pass.load().unwrap(); - pass.attach("lo", XdpFlags::default()).unwrap(); - - println!("Loading Extension Program"); - let mut bpf = BpfLoader::new().extension("drop").load_file("ext.o").unwrap(); - let drop_: &mut Extension = bpf.program_mut("drop").unwrap().try_into().unwrap(); - drop_.load(pass.fd().unwrap(), "xdp_pass").unwrap(); - println!("Success..."); -} diff --git a/test/cases/000_smoke/010_ext/test.sh b/test/cases/000_smoke/010_ext/test.sh deleted file mode 100755 index 695849a5..00000000 --- a/test/cases/000_smoke/010_ext/test.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -# SUMMARY: Check that a simple XDP program an be loaded -# LABELS: - -set -e - -# Source libraries. Uncomment if needed/defined -#. "${RT_LIB}" -. "${RT_PROJECT_ROOT}/_lib/lib.sh" - -NAME=ext - -clean_up() { - rm -rf main.o ${NAME}.o ${NAME} - exec_vm rm -f main.o ${NAME}.o ${NAME} -} - -trap clean_up EXIT - -# Test code goes here -min_kernel_version 5.9 - -compile_c_ebpf "$(pwd)/main.bpf.c" -compile_c_ebpf "$(pwd)/${NAME}.bpf.c" -compile_user "$(pwd)/${NAME}.rs" - -scp_vm main.o -scp_vm ${NAME}.o -scp_vm ${NAME} - -exec_vm sudo ./${NAME} - -exit 0 \ No newline at end of file diff --git a/test/cases/000_smoke/group.sh b/test/cases/000_smoke/group.sh deleted file mode 100755 index d60a423d..00000000 --- a/test/cases/000_smoke/group.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh -# SUMMARY: Smoke tests to check that simple programs can be loaded on a VM -# LABELS: - -# Source libraries. Uncomment if needed/defined -# . "${RT_LIB}" -. "${RT_PROJECT_ROOT}/_lib/lib.sh" - -set -e - -group_init() { - # Group initialisation code goes here - return 0 -} - -group_deinit() { - # Group de-initialisation code goes here - return 0 -} - -CMD=$1 -case $CMD in -init) - group_init - res=$? - ;; -deinit) - group_deinit - res=$? - ;; -*) - res=1 - ;; -esac - -exit $res \ No newline at end of file diff --git a/test/cases/010_load/000_name/name_test.rs b/test/cases/010_load/000_name/name_test.rs deleted file mode 100755 index 0e9c9bd4..00000000 --- a/test/cases/010_load/000_name/name_test.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! ```cargo -//! [dependencies] -//! aya = { path = "../../../../aya" } -//! ``` - -use aya::{ - Bpf, - programs::{Xdp, XdpFlags}, -}; -use std::convert::TryInto; -use std::{thread, time}; - -fn main() { - println!("Loading XDP program"); - let mut bpf = Bpf::load_file("name_test.o").unwrap(); - let dispatcher: &mut Xdp = bpf.program_mut("ihaveaverylongname").unwrap().try_into().unwrap(); - dispatcher.load().unwrap(); - dispatcher.attach("eth0", XdpFlags::default()).unwrap(); - thread::sleep(time::Duration::from_secs(20)); -} diff --git a/test/cases/010_load/000_name/test.sh b/test/cases/010_load/000_name/test.sh deleted file mode 100755 index 1fbabb9f..00000000 --- a/test/cases/010_load/000_name/test.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh -# SUMMARY: Check that long names are properly truncated -# LABELS: - -set -e - -# Source libraries. Uncomment if needed/defined -#. "${RT_LIB}" -. "${RT_PROJECT_ROOT}/_lib/lib.sh" - -NAME=name_test - -clean_up() { - rm -rf ebpf user ${NAME}.o ${NAME} - exec_vm sudo pkill -9 ${NAME} - exec_vm rm ${NAME} ${NAME}.o -} - -trap clean_up EXIT - -# Test code goes here -compile_ebpf ${NAME}.ebpf.rs -compile_user ${NAME}.rs - -scp_vm ${NAME}.o -scp_vm ${NAME} - -exec_vm sudo ./${NAME}& -prog_list=$(exec_vm sudo bpftool prog) -echo "${prog_list}" | grep -q "xdp name ihaveaverylongn tag" - -exit 0 \ No newline at end of file diff --git a/test/cases/010_load/010_multiple_maps/multimap.rs b/test/cases/010_load/010_multiple_maps/multimap.rs deleted file mode 100755 index 48d7548c..00000000 --- a/test/cases/010_load/010_multiple_maps/multimap.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! ```cargo -//! [dependencies] -//! log = "0.4" -//! simplelog = "0.11" -//! aya = { path = "../../../../aya" } -//! ``` - -use aya::{ - Bpf, - programs::{Xdp, XdpFlags}, -}; -use log::info; -use std::convert::TryInto; - -use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode}; - -fn main() { - TermLogger::init( - LevelFilter::Debug, - ConfigBuilder::new() - .set_target_level(LevelFilter::Error) - .set_location_level(LevelFilter::Error) - .build(), - TerminalMode::Mixed, - ColorChoice::Auto, - ).unwrap(); - info!("Loading XDP program"); - let mut bpf = Bpf::load_file("multimap.o").unwrap(); - let pass: &mut Xdp = bpf.program_mut("stats").unwrap().try_into().unwrap(); - pass.load().unwrap(); - pass.attach("eth0", XdpFlags::default()).unwrap(); - info!("Success..."); -} diff --git a/test/cases/010_load/010_multiple_maps/test.sh b/test/cases/010_load/010_multiple_maps/test.sh deleted file mode 100755 index 75def301..00000000 --- a/test/cases/010_load/010_multiple_maps/test.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh -# SUMMARY: Check that a program with multiple maps in the maps section loads -# LABELS: - -set -e - -# Source libraries. Uncomment if needed/defined -#. "${RT_LIB}" -. "${RT_PROJECT_ROOT}/_lib/lib.sh" - -NAME=multimap - -clean_up() { - rm -rf ${NAME}.o ${NAME} - exec_vm rm -f ${NAME}.o ${NAME} -} - -trap clean_up EXIT - -# Test code goes here -compile_c_ebpf "$(pwd)/${NAME}.bpf.c" -compile_user "$(pwd)/${NAME}.rs" - -scp_vm ${NAME}.o -scp_vm ${NAME} - -exec_vm sudo ./${NAME} - -exit 0 \ No newline at end of file diff --git a/test/cases/010_load/030_unload/test.rs b/test/cases/010_load/030_unload/test.rs deleted file mode 100755 index ed0d725b..00000000 --- a/test/cases/010_load/030_unload/test.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! ```cargo -//! [dependencies] -//! aya = { path = "../../../../aya" } -//! ``` - -use aya::{ - programs::{Xdp, XdpFlags}, - Bpf, -}; -use std::convert::TryInto; -use std::process::Command; - -fn is_loaded() -> bool { - let output = Command::new("bpftool").args(&["prog"]).output().unwrap(); - let stdout = String::from_utf8(output.stdout).unwrap(); - stdout.contains("test_unload") -} - -fn assert_loaded(loaded: bool) { - let state = is_loaded(); - if state == loaded { - return; - } - panic!("Expected loaded: {} but was loaded: {}", loaded, state); -} - -fn main() { - println!("Loading XDP program"); - let mut bpf = Bpf::load_file("test.o").unwrap(); - let dispatcher: &mut Xdp = bpf.program_mut("test_unload").unwrap().try_into().unwrap(); - - dispatcher.load().unwrap(); - - let link = dispatcher.attach("eth0", XdpFlags::default()).unwrap(); - - { - let link_owned = dispatcher.take_link(link); - - dispatcher.unload().unwrap(); - - assert_loaded(true); - }; - - assert_loaded(false); - - dispatcher.load().unwrap(); - - assert_loaded(true); - - dispatcher.attach("eth0", XdpFlags::default()).unwrap(); - - assert_loaded(true); - - dispatcher.unload().unwrap(); - - assert_loaded(false); -} diff --git a/test/cases/010_load/030_unload/test.sh b/test/cases/010_load/030_unload/test.sh deleted file mode 100755 index efe20416..00000000 --- a/test/cases/010_load/030_unload/test.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh -# SUMMARY: Check that the program can be unloaded -# LABELS: - -set -ex - -# Source libraries. Uncomment if needed/defined -#. "${RT_LIB}" -. "${RT_PROJECT_ROOT}/_lib/lib.sh" - -NAME=test - -clean_up() { - rm -rf ebpf user ${NAME}.o ${NAME} - exec_vm rm ${NAME} ${NAME}.o -} - -trap clean_up EXIT - -# Test code goes here -compile_ebpf ${NAME}.ebpf.rs -compile_user ${NAME}.rs - -scp_vm ${NAME}.o -scp_vm ${NAME} - -exec_vm sudo ./${NAME} \ No newline at end of file diff --git a/test/cases/010_load/group.sh b/test/cases/010_load/group.sh deleted file mode 100755 index 74bab3a7..00000000 --- a/test/cases/010_load/group.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh -# SUMMARY: Tests to check loader features -# LABELS: - -# Source libraries. Uncomment if needed/defined -# . "${RT_LIB}" -. "${RT_PROJECT_ROOT}/_lib/lib.sh" - -set -e - -group_init() { - # Group initialisation code goes here - return 0 -} - -group_deinit() { - # Group de-initialisation code goes here - return 0 -} - -CMD=$1 -case $CMD in -init) - group_init - res=$? - ;; -deinit) - group_deinit - res=$? - ;; -*) - res=1 - ;; -esac - -exit $res \ No newline at end of file diff --git a/test/cases/020_elf/000_maps/map_test.o b/test/cases/020_elf/000_maps/map_test.o deleted file mode 100644 index 522a86d2854b473430996eceb94439a930fa1d78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 107384 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˸&n3%2>0Syhj?~v_$j&xA_b6MWL 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 diff --git a/test/cases/020_elf/000_maps/test.sh b/test/cases/020_elf/000_maps/test.sh deleted file mode 100755 index 3ce0f4cc..00000000 --- a/test/cases/020_elf/000_maps/test.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh -# SUMMARY: Check that maps are correctly represented in ELF files -# LABELS: - -set -ex - -# Source libraries. Uncomment if needed/defined -#. "${RT_LIB}" -. "${RT_PROJECT_ROOT}/_lib/lib.sh" - -NAME=map_test - -clean_up() { - rm -rf ebpf user ${NAME}.o -} - -trap clean_up EXIT - -# Test code goes here -compile_ebpf ${NAME}.ebpf.rs - -readelf --sections ${NAME}.o | grep -q "maps" -readelf --syms ${NAME}.o | grep -q "BAR" - -exit 0 \ No newline at end of file diff --git a/test/cases/020_elf/group.sh b/test/cases/020_elf/group.sh deleted file mode 100644 index 9a46609c..00000000 --- a/test/cases/020_elf/group.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh -# SUMMARY: Tests to check ELF from aya-bpf -# LABELS: - -# Source libraries. Uncomment if needed/defined -# . "${RT_LIB}" -. "${RT_PROJECT_ROOT}/_lib/lib.sh" - -set -e - -group_init() { - # Group initialisation code goes here - return 0 -} - -group_deinit() { - # Group de-initialisation code goes here - return 0 -} - -CMD=$1 -case $CMD in -init) - group_init - res=$? - ;; -deinit) - group_deinit - res=$? - ;; -*) - res=1 - ;; -esac - -exit $res \ No newline at end of file diff --git a/test/cases/_lib/compile-ebpf.ers b/test/cases/_lib/compile-ebpf.ers deleted file mode 100644 index 66993d20..00000000 --- a/test/cases/_lib/compile-ebpf.ers +++ /dev/null @@ -1,85 +0,0 @@ -//! ```cargo -//! [dependencies] -//! libbpf-sys = { version = "0.6.1-1" } -//! anyhow = "1" -//! ``` - -use std::{ - env, - fs::{self, OpenOptions}, - io::Write, - path::Path, - process::Command, - string::String, -}; -use anyhow::{bail, Context, Result}; -static CLANG_DEFAULT: &str = "/usr/bin/clang"; - -/// Extract vendored libbpf headers from libbpf-sys. -fn extract_libbpf_headers>(include_path: P) -> Result<()> { - let dir = include_path.as_ref().join("bpf"); - fs::create_dir_all(&dir)?; - for (filename, contents) in libbpf_sys::API_HEADERS.iter() { - let path = dir.as_path().join(filename); - let mut file = OpenOptions::new().write(true).create(true).open(path)?; - file.write_all(contents.as_bytes())?; - } - - Ok(()) -} - -/// Build eBPF programs with clang and libbpf headers. -fn build_ebpf>(in_file: P, out_file: P, include_path: P) -> Result<()> { - extract_libbpf_headers(include_path.clone())?; - let clang = match env::var("CLANG") { - Ok(val) => val, - Err(_) => String::from(CLANG_DEFAULT), - }; - let arch = match std::env::consts::ARCH { - "x86_64" => "x86", - "aarch64" => "arm64", - _ => std::env::consts::ARCH, - }; - let mut cmd = Command::new(clang); - cmd.arg(format!("-I{}", include_path.as_ref().to_string_lossy())) - .arg("-g") - .arg("-O2") - .arg("-target") - .arg("bpf") - .arg("-c") - .arg(format!("-D__TARGET_ARCH_{}", arch)) - .arg(in_file.as_ref().as_os_str()) - .arg("-o") - .arg(out_file.as_ref().as_os_str()); - - let output = cmd.output().context("Failed to execute clang")?; - if !output.status.success() { - bail!( - "Failed to compile eBPF programs\n \ - stdout=\n \ - {}\n \ - stderr=\n \ - {}\n", - String::from_utf8(output.stdout).unwrap(), - String::from_utf8(output.stderr).unwrap() - ); - } - - Ok(()) -} - -fn main() -> Result<()> { - let args: Vec = env::args().collect(); - if args.len() != 3 { - bail!("requires 2 arguments. src and dst") - } - let path = env::current_dir()?; - let src = Path::new(&args[1]); - let dst = Path::new(&args[2]); - - let include_path = path.join("include"); - fs::create_dir_all(include_path.clone())?; - build_ebpf(src, dst, &include_path)?; - - Ok(()) -} \ No newline at end of file diff --git a/test/cases/group.sh b/test/cases/group.sh deleted file mode 100755 index b1362948..00000000 --- a/test/cases/group.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh -# NAME: aya -# SUMMARY: Aya Regression Tests - -# Source libraries. Uncomment if needed/defined -# . "${RT_LIB}" -. "${RT_PROJECT_ROOT}/_lib/lib.sh" - -group_init() { - # Group initialisation code goes here - [ -r "${AYA_TMPDIR}" ] && rm -rf "${AYA_TMPDIR}" - mkdir "${AYA_TMPDIR}" - start_vm -} - -group_deinit() { - # Group de-initialisation code goes here - stop_vm -} - -CMD=$1 -case $CMD in -init) - group_init - res=$? - ;; -deinit) - group_deinit - res=$? - ;; -*) - res=1 - ;; -esac - -exit $res diff --git a/test/integration-ebpf/.cargo/config.toml b/test/integration-ebpf/.cargo/config.toml new file mode 100644 index 00000000..d89a98b0 --- /dev/null +++ b/test/integration-ebpf/.cargo/config.toml @@ -0,0 +1,6 @@ +[build] +target-dir = "../../target" +target = "bpfel-unknown-none" + +[unstable] +build-std = ["core"] diff --git a/test/integration-ebpf/Cargo.toml b/test/integration-ebpf/Cargo.toml new file mode 100644 index 00000000..b2a526fa --- /dev/null +++ b/test/integration-ebpf/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "integration-ebpf" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +aya-bpf = { path = "../../bpf/aya-bpf" } + +[[bin]] +name = "map_test" +path = "src/map_test.rs" + +[[bin]] +name = "name_test" +path = "src/name_test.rs" + +[[bin]] +name = "pass" +path = "src/pass.rs" + +[[bin]] +name = "test" +path = "src/test.rs" + +[profile.dev] +panic = "abort" +opt-level = 2 +overflow-checks = false + +[profile.release] +panic = "abort" +debug = 2 + +[workspace] +members = [] diff --git a/test/integration-ebpf/rust-toolchain.toml b/test/integration-ebpf/rust-toolchain.toml new file mode 100644 index 00000000..c046a094 --- /dev/null +++ b/test/integration-ebpf/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel="nightly" diff --git a/test/integration-ebpf/rustfmt.toml b/test/integration-ebpf/rustfmt.toml new file mode 120000 index 00000000..760eb840 --- /dev/null +++ b/test/integration-ebpf/rustfmt.toml @@ -0,0 +1 @@ +../../rustfmt.toml \ No newline at end of file diff --git a/test/cases/000_smoke/010_ext/ext.bpf.c b/test/integration-ebpf/src/bpf/ext.bpf.c similarity index 65% rename from test/cases/000_smoke/010_ext/ext.bpf.c rename to test/integration-ebpf/src/bpf/ext.bpf.c index f0da9445..6c77127a 100644 --- a/test/cases/000_smoke/010_ext/ext.bpf.c +++ b/test/integration-ebpf/src/bpf/ext.bpf.c @@ -1,6 +1,5 @@ #include #include -#include SEC("xdp/drop") int xdp_drop(struct xdp_md *ctx) @@ -8,4 +7,4 @@ int xdp_drop(struct xdp_md *ctx) return XDP_DROP; } -char _license[] SEC("license") = "GPL"; \ No newline at end of file +char _license[] SEC("license") = "GPL"; diff --git a/test/cases/000_smoke/010_ext/main.bpf.c b/test/integration-ebpf/src/bpf/main.bpf.c similarity index 65% rename from test/cases/000_smoke/010_ext/main.bpf.c rename to test/integration-ebpf/src/bpf/main.bpf.c index fbe4600b..79c82041 100644 --- a/test/cases/000_smoke/010_ext/main.bpf.c +++ b/test/integration-ebpf/src/bpf/main.bpf.c @@ -1,6 +1,5 @@ #include #include -#include SEC("xdp/pass") int xdp_pass(struct xdp_md *ctx) @@ -8,4 +7,4 @@ int xdp_pass(struct xdp_md *ctx) return XDP_PASS; } -char _license[] SEC("license") = "GPL"; \ No newline at end of file +char _license[] SEC("license") = "GPL"; diff --git a/test/cases/010_load/010_multiple_maps/multimap.bpf.c b/test/integration-ebpf/src/bpf/multimap.bpf.c similarity index 96% rename from test/cases/010_load/010_multiple_maps/multimap.bpf.c rename to test/integration-ebpf/src/bpf/multimap.bpf.c index 82ad2832..615935eb 100644 --- a/test/cases/010_load/010_multiple_maps/multimap.bpf.c +++ b/test/integration-ebpf/src/bpf/multimap.bpf.c @@ -44,4 +44,4 @@ int xdp_stats(struct xdp_md *ctx) return XDP_PASS; } -char _license[] SEC("license") = "GPL"; \ No newline at end of file +char _license[] SEC("license") = "GPL"; diff --git a/test/cases/020_elf/000_maps/map_test.ebpf.rs b/test/integration-ebpf/src/map_test.rs similarity index 87% rename from test/cases/020_elf/000_maps/map_test.ebpf.rs rename to test/integration-ebpf/src/map_test.rs index c8d45df3..fc17d1f9 100644 --- a/test/cases/020_elf/000_maps/map_test.ebpf.rs +++ b/test/integration-ebpf/src/map_test.rs @@ -1,16 +1,11 @@ -//! ```cargo -//! [dependencies] -//! aya-bpf = { path = "../../../../bpf/aya-bpf" } -//! ``` - #![no_std] #![no_main] use aya_bpf::{ bindings::xdp_action, macros::{map, xdp}, - programs::XdpContext, maps::Array, + programs::XdpContext, }; #[map] diff --git a/test/cases/010_load/000_name/name_test.ebpf.rs b/test/integration-ebpf/src/name_test.rs similarity index 57% rename from test/cases/010_load/000_name/name_test.ebpf.rs rename to test/integration-ebpf/src/name_test.rs index e3cb6d21..f4f1e315 100644 --- a/test/cases/010_load/000_name/name_test.ebpf.rs +++ b/test/integration-ebpf/src/name_test.rs @@ -1,19 +1,10 @@ -//! ```cargo -//! [dependencies] -//! aya-bpf = { path = "../../../../bpf/aya-bpf" } -//! ``` - #![no_std] #![no_main] -use aya_bpf::{ - bindings::xdp_action, - macros::xdp, - programs::XdpContext, -}; +use aya_bpf::{bindings::xdp_action, macros::xdp, programs::XdpContext}; -#[xdp(name="ihaveaverylongname")] -pub fn pass(ctx: XdpContext) -> u32 { +#[xdp(name = "ihaveaverylongname")] +pub fn ihaveaverylongname(ctx: XdpContext) -> u32 { match unsafe { try_pass(ctx) } { Ok(ret) => ret, Err(_) => xdp_action::XDP_ABORTED, @@ -27,4 +18,4 @@ unsafe fn try_pass(_ctx: XdpContext) -> Result { #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { unsafe { core::hint::unreachable_unchecked() } -} \ No newline at end of file +} diff --git a/test/cases/000_smoke/000_xdp/pass.ebpf.rs b/test/integration-ebpf/src/pass.rs similarity index 65% rename from test/cases/000_smoke/000_xdp/pass.ebpf.rs rename to test/integration-ebpf/src/pass.rs index 1097209e..0979d557 100644 --- a/test/cases/000_smoke/000_xdp/pass.ebpf.rs +++ b/test/integration-ebpf/src/pass.rs @@ -1,18 +1,9 @@ -//! ```cargo -//! [dependencies] -//! aya-bpf = { path = "../../../../bpf/aya-bpf" } -//! ``` - #![no_std] #![no_main] -use aya_bpf::{ - bindings::xdp_action, - macros::xdp, - programs::XdpContext, -}; +use aya_bpf::{bindings::xdp_action, macros::xdp, programs::XdpContext}; -#[xdp(name="pass")] +#[xdp(name = "pass")] pub fn pass(ctx: XdpContext) -> u32 { match unsafe { try_pass(ctx) } { Ok(ret) => ret, @@ -27,4 +18,4 @@ unsafe fn try_pass(_ctx: XdpContext) -> Result { #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { unsafe { core::hint::unreachable_unchecked() } -} \ No newline at end of file +} diff --git a/test/cases/010_load/030_unload/test.ebpf.rs b/test/integration-ebpf/src/test.rs similarity index 84% rename from test/cases/010_load/030_unload/test.ebpf.rs rename to test/integration-ebpf/src/test.rs index a6ed58dd..d0ff7b32 100644 --- a/test/cases/010_load/030_unload/test.ebpf.rs +++ b/test/integration-ebpf/src/test.rs @@ -1,8 +1,3 @@ -//! ```cargo -//! [dependencies] -//! aya-bpf = { path = "../../../../bpf/aya-bpf" } -//! ``` - #![no_std] #![no_main] diff --git a/test/integration-test-macros/Cargo.toml b/test/integration-test-macros/Cargo.toml new file mode 100644 index 00000000..57d928e1 --- /dev/null +++ b/test/integration-test-macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "integration-test-macros" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +quote = "1" +syn = {version = "1.0", features = ["full"]} + +[lib] +proc-macro = true diff --git a/test/integration-test-macros/src/lib.rs b/test/integration-test-macros/src/lib.rs new file mode 100644 index 00000000..6f69b486 --- /dev/null +++ b/test/integration-test-macros/src/lib.rs @@ -0,0 +1,19 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, ItemFn}; + +#[proc_macro_attribute] +pub fn integration_test(_attr: TokenStream, item: TokenStream) -> TokenStream { + let item = parse_macro_input!(item as ItemFn); + let name = &item.sig.ident; + let name_str = &item.sig.ident.to_string(); + let expanded = quote! { + #item + + inventory::submit!(IntegrationTest { + name: concat!(module_path!(), "::", #name_str), + test_fn: #name, + }); + }; + TokenStream::from(expanded) +} diff --git a/test/integration-test/Cargo.toml b/test/integration-test/Cargo.toml new file mode 100644 index 00000000..decae299 --- /dev/null +++ b/test/integration-test/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "integration-test" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +anyhow = "1" +aya = { path = "../../aya" } +inventory = "0.2" +integration-test-macros = { path = "../integration-test-macros" } +lazy_static = "1" +libc = { version = "0.2.105" } +log = "0.4" +object = { version = "0.29", default-features = false, features = ["std", "read_core", "elf"] } +regex = "1" +simplelog = "0.12" diff --git a/test/integration-test/src/main.rs b/test/integration-test/src/main.rs new file mode 100644 index 00000000..f3f61b3a --- /dev/null +++ b/test/integration-test/src/main.rs @@ -0,0 +1,27 @@ +use log::info; +use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode}; + +mod tests; +use tests::IntegrationTest; + +fn main() -> anyhow::Result<()> { + TermLogger::init( + LevelFilter::Debug, + ConfigBuilder::new() + .set_target_level(LevelFilter::Error) + .set_location_level(LevelFilter::Error) + .build(), + TerminalMode::Mixed, + ColorChoice::Auto, + )?; + + // Run the tests + for t in inventory::iter:: { + info!("Running {}", t.name); + if let Err(e) = (t.test_fn)() { + panic!("{}", e) + } + } + + Ok(()) +} diff --git a/test/integration-test/src/tests/elf.rs b/test/integration-test/src/tests/elf.rs new file mode 100644 index 00000000..f139dfaf --- /dev/null +++ b/test/integration-test/src/tests/elf.rs @@ -0,0 +1,27 @@ +use super::{integration_test, IntegrationTest}; + +use anyhow::bail; +use aya::include_bytes_aligned; +use object::{Object, ObjectSymbol}; + +#[integration_test] +fn test_maps() -> anyhow::Result<()> { + let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/map_test"); + let obj_file = object::File::parse(bytes)?; + if obj_file.section_by_name("maps").is_none() { + bail!("No 'maps' ELF section"); + } + let mut found = false; + for sym in obj_file.symbols() { + if let Ok(name) = sym.name() { + if name == "BAR" { + found = true; + break; + } + } + } + if !found { + bail!("No symbol 'BAR' in ELF file") + } + Ok(()) +} diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs new file mode 100644 index 00000000..bacbf317 --- /dev/null +++ b/test/integration-test/src/tests/load.rs @@ -0,0 +1,75 @@ +use std::{convert::TryInto, process::Command}; + +use aya::{ + include_bytes_aligned, + programs::{Xdp, XdpFlags}, + Bpf, +}; + +use super::{integration_test, IntegrationTest}; + +#[integration_test] +fn long_name() -> anyhow::Result<()> { + let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/name_test"); + let mut bpf = Bpf::load(bytes)?; + let name_prog: &mut Xdp = bpf.program_mut("ihaveaverylongname").unwrap().try_into()?; + name_prog.load().unwrap(); + name_prog.attach("lo", XdpFlags::default())?; + + // We used to be able to assert with bpftool that the program name was short. + // It seem though that it now uses the name from the ELF symbol table instead. + // Therefore, as long as we were able to load the program, this is good enough. + + Ok(()) +} + +#[integration_test] +fn multiple_maps() -> anyhow::Result<()> { + let bytes = + include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/multimap.bpf.o"); + let mut bpf = Bpf::load(bytes)?; + let pass: &mut Xdp = bpf.program_mut("stats").unwrap().try_into().unwrap(); + pass.load().unwrap(); + pass.attach("lo", XdpFlags::default()).unwrap(); + Ok(()) +} + +fn is_loaded() -> bool { + let output = Command::new("bpftool").args(&["prog"]).output().unwrap(); + let stdout = String::from_utf8(output.stdout).unwrap(); + stdout.contains("test_unload") +} + +fn assert_loaded(loaded: bool) { + let state = is_loaded(); + if state == loaded { + return; + } + panic!("Expected loaded: {} but was loaded: {}", loaded, state); +} + +#[integration_test] +fn unload() -> anyhow::Result<()> { + let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/test"); + let mut bpf = Bpf::load(bytes)?; + let prog: &mut Xdp = bpf.program_mut("test_unload").unwrap().try_into().unwrap(); + prog.load().unwrap(); + let link = prog.attach("lo", XdpFlags::default()).unwrap(); + { + let _link_owned = prog.take_link(link); + prog.unload().unwrap(); + assert_loaded(true); + }; + + assert_loaded(false); + prog.load().unwrap(); + + assert_loaded(true); + prog.attach("lo", XdpFlags::default()).unwrap(); + + assert_loaded(true); + prog.unload().unwrap(); + + assert_loaded(false); + Ok(()) +} diff --git a/test/integration-test/src/tests/mod.rs b/test/integration-test/src/tests/mod.rs new file mode 100644 index 00000000..2dd5524a --- /dev/null +++ b/test/integration-test/src/tests/mod.rs @@ -0,0 +1,37 @@ +use anyhow::bail; +use lazy_static::lazy_static; +use libc::{uname, utsname}; +use regex::Regex; +use std::{ffi::CStr, mem}; + +pub mod elf; +pub mod load; +pub mod smoke; + +pub use integration_test_macros::integration_test; +#[derive(Debug)] +pub struct IntegrationTest { + pub name: &'static str, + pub test_fn: fn() -> anyhow::Result<()>, +} + +pub(crate) fn kernel_version() -> anyhow::Result<(u8, u8, u8)> { + lazy_static! { + static ref RE: Regex = Regex::new(r"^([0-9]+)\.([0-9]+)\.([0-9]+)").unwrap(); + } + let mut data: utsname = unsafe { mem::zeroed() }; + let ret = unsafe { uname(&mut data) }; + assert!(ret >= 0, "libc::uname failed."); + let release_cstr = unsafe { CStr::from_ptr(data.release.as_ptr()) }; + let release = release_cstr.to_string_lossy(); + if let Some(caps) = RE.captures(&release) { + let major = caps.get(1).unwrap().as_str().parse().unwrap(); + let minor = caps.get(2).unwrap().as_str().parse().unwrap(); + let patch = caps.get(3).unwrap().as_str().parse().unwrap(); + Ok((major, minor, patch)) + } else { + bail!("no kernel version found"); + } +} + +inventory::collect!(IntegrationTest); diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/src/tests/smoke.rs new file mode 100644 index 00000000..a4474764 --- /dev/null +++ b/test/integration-test/src/tests/smoke.rs @@ -0,0 +1,45 @@ +use std::convert::TryInto; + +use aya::{ + include_bytes_aligned, + programs::{Extension, Xdp, XdpFlags}, + Bpf, BpfLoader, +}; +use log::info; + +use super::{integration_test, kernel_version, IntegrationTest}; + +#[integration_test] +fn xdp() -> anyhow::Result<()> { + let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/pass"); + let mut bpf = Bpf::load(bytes)?; + let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); + dispatcher.load().unwrap(); + dispatcher.attach("lo", XdpFlags::default()).unwrap(); + Ok(()) +} + +#[integration_test] +fn extension() -> anyhow::Result<()> { + let (major, minor, _) = kernel_version()?; + if major < 5 || minor < 9 { + info!( + "skipping as {}.{} does not meet version requirement of 5.9", + major, minor + ); + return Ok(()); + } + // TODO: Check kernel version == 5.9 or later + let main_bytes = + include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/main.bpf.o"); + let mut bpf = Bpf::load(main_bytes)?; + let pass: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); + pass.load().unwrap(); + pass.attach("lo", XdpFlags::default()).unwrap(); + + let ext_bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/ext.bpf.o"); + let mut bpf = BpfLoader::new().extension("drop").load(ext_bytes).unwrap(); + let drop_: &mut Extension = bpf.program_mut("drop").unwrap().try_into().unwrap(); + drop_.load(pass.fd().unwrap(), "xdp_pass").unwrap(); + Ok(()) +} diff --git a/test/cases/_lib/lib.sh b/test/run.sh old mode 100644 new mode 100755 similarity index 63% rename from test/cases/_lib/lib.sh rename to test/run.sh index 2df42b8f..f144842b --- a/test/cases/_lib/lib.sh +++ b/test/run.sh @@ -1,16 +1,12 @@ #!/bin/sh -# Source the main regression test library if present -[ -f "${RT_LIB}" ] && . "${RT_LIB}" +set -e # Temporary directory for tests to use. -AYA_TMPDIR="${RT_PROJECT_ROOT}/_tmp" +AYA_TMPDIR="$(pwd)/.tmp" # Directory for VM images -AYA_IMGDIR="${RT_PROJECT_ROOT}/_images" - -# Cancel Exit Code -RT_CANCEL=253 +AYA_IMGDIR=${AYA_TMPDIR} # Test Architecture if [ -z "${AYA_TEST_ARCH}" ]; then @@ -27,71 +23,6 @@ case "${AYA_TEST_IMAGE}" in centos*) AYA_SSH_USER="centos";; esac -# compiles the ebpf program by using rust-script to create a temporary -# cargo project in $(pwd)/ebpf. caller must add rm -rf ebpf to the clean_up -# functionAYA_TEST_ARCH -compile_ebpf() { - file=$(basename "$1") - dir=$(dirname "$1") - base=$(echo "${file}" | cut -f1 -d '.') - - rm -rf "${dir}/ebpf" - - rust-script --pkg-path "${dir}/ebpf" --gen-pkg-only "$1" - artifact=$(sed -n 's/^name = \"\(.*\)\"/\1/p' "${dir}/ebpf/Cargo.toml" | head -n1) - - mkdir -p "${dir}/.cargo" - cat > "${dir}/.cargo/config.toml" << EOF -[build] -target = "bpfel-unknown-none" - -[unstable] -build-std = ["core"] -EOF - cat >> "${dir}/ebpf/Cargo.toml" << EOF -[workspace] -members = [] -EOF - # overwrite the rs file as rust-script adds a main fn - cp "$1" "${dir}/ebpf/${file}" - cargo build -q --manifest-path "${dir}/ebpf/Cargo.toml" - mv "${dir}/ebpf/target/bpfel-unknown-none/debug/${artifact}" "${dir}/${base}.o" - rm -rf "${dir}/.cargo" - rm -rf "${dir}/ebpf" -} - -# compile a C BPF file -compile_c_ebpf() { - file=$(basename "$1") - dir=$(dirname "$1") - base=$(echo "${file}" | cut -f1 -d '.') - - rust-script "${RT_PROJECT_ROOT}/_lib/compile-ebpf.ers" "${1}" "${dir}/${base}.o" - rm -rf "${dir}/include" -} - -# compiles the userspace program by using rust-script to create a temporary -# cargo project in $(pwd)/user. caller must add rm -rf ebpf to the clean_up -# function. this is required since the binary produced has to be run with -# sudo to load an eBPF program -compile_user() { - file=$(basename "$1") - dir=$(dirname "$1") - base=$(echo "${file}" | cut -f1 -d '.') - - rm -rf "${dir}/user" - - rust-script --pkg-path "${dir}/user" --gen-pkg-only "$1" - artifact=$(sed -n 's/^name = \"\(.*\)\"/\1/p' "${dir}/user/Cargo.toml" | head -n1) - cat >> "${dir}/user/Cargo.toml" << EOF -[workspace] -members = [] -EOF - cargo build -q --release --manifest-path "${dir}/user/Cargo.toml" --target=x86_64-unknown-linux-musl - mv "${dir}/user/target/x86_64-unknown-linux-musl/release/${artifact}" "${dir}/${base}" - rm -rf "${dir}/user" -} - download_images() { mkdir -p "${AYA_IMGDIR}" case $1 in @@ -214,10 +145,11 @@ EOF scp_vm() { local=$1 + remote=$(basename "$1") scp -q -F "${AYA_TMPDIR}/ssh_config" \ -i "${AYA_TMPDIR}/test_rsa" \ -P 2222 "${local}" \ - "${AYA_SSH_USER}@localhost:${local}" + "${AYA_SSH_USER}@localhost:${remote}" } exec_vm() { @@ -243,19 +175,14 @@ cleanup_vm() { fi } -# Check that host machine meets minimum kernel requirement -# Must be in format {major}.{minor} -min_kernel_version() { - target_major=$(echo "$1" | cut -d '.' -f1) - target_minor=$(echo "$1" | cut -d '.' -f2) +if [ -z "$1" ]; then + echo "path to libbpf required" + exit 1 +fi - vm_kernel=$(exec_vm uname -r) - vm_major=$(echo "${vm_kernel}" | cut -d '.' -f1) - vm_minor=$(echo "${vm_kernel}" | cut -d '.' -f2) +start_vm +trap stop_vm EXIT - if [ "${vm_major}" -lt "${target_major}" ] || [ "${vm_minor}" -lt "${target_minor}" ]; then - echo "Test not supported on kernel ${vm_major}.${vm_minor}" - return ${RT_CANCEL} - fi - return 0 -} \ No newline at end of file +cargo xtask build-integration-test --musl --libbpf-dir "$1" +scp_vm ../target/x86_64-unknown-linux-musl/debug/integration-test +exec_vm sudo ./integration-test diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 6a709059..9f8a3820 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -13,3 +13,5 @@ quote = "1" proc-macro2 = "1" indexmap = "1.6" indoc = "1.0" +lazy_static = "1" +serde_json = "1" diff --git a/xtask/src/build_ebpf.rs b/xtask/src/build_ebpf.rs new file mode 100644 index 00000000..1ba16b2d --- /dev/null +++ b/xtask/src/build_ebpf.rs @@ -0,0 +1,162 @@ +use std::{ + env, fs, + path::{Path, PathBuf}, + process::Command, +}; + +use anyhow::{bail, Context}; +use clap::Parser; + +use crate::utils::WORKSPACE_ROOT; + +#[derive(Debug, Copy, Clone)] +pub enum Architecture { + BpfEl, + BpfEb, +} + +impl std::str::FromStr for Architecture { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(match s { + "bpfel-unknown-none" => Architecture::BpfEl, + "bpfeb-unknown-none" => Architecture::BpfEb, + _ => return Err("invalid target".to_owned()), + }) + } +} + +impl std::fmt::Display for Architecture { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Architecture::BpfEl => "bpfel-unknown-none", + Architecture::BpfEb => "bpfeb-unknown-none", + }) + } +} + +#[derive(Debug, Parser)] +pub struct Options { + /// Set the endianness of the BPF target + #[clap(default_value = "bpfel-unknown-none", long)] + pub target: Architecture, + /// Build the release target + #[clap(long)] + pub release: bool, + /// Libbpf dir, required for compiling C code + #[clap(long, action)] + pub libbpf_dir: PathBuf, +} + +pub fn build_ebpf(opts: Options) -> anyhow::Result<()> { + build_rust_ebpf(&opts)?; + build_c_ebpf(&opts) +} + +fn build_rust_ebpf(opts: &Options) -> anyhow::Result<()> { + let mut dir = PathBuf::from(WORKSPACE_ROOT.to_string()); + dir.push("test/integration-ebpf"); + + let target = format!("--target={}", opts.target); + let mut args = vec![ + "+nightly", + "build", + "--verbose", + target.as_str(), + "-Z", + "build-std=core", + ]; + if opts.release { + args.push("--release") + } + let status = Command::new("cargo") + .current_dir(&dir) + .args(&args) + .status() + .expect("failed to build bpf program"); + assert!(status.success()); + Ok(()) +} + +fn get_libbpf_headers>(libbpf_dir: P, include_path: P) -> anyhow::Result<()> { + let dir = include_path.as_ref(); + fs::create_dir_all(&dir)?; + let status = Command::new("make") + .current_dir(libbpf_dir.as_ref().join("src")) + .arg(format!("INCLUDEDIR={}", dir.as_os_str().to_string_lossy())) + .arg("install_headers") + .status() + .expect("failed to build get libbpf headers"); + assert!(status.success()); + Ok(()) +} + +fn build_c_ebpf(opts: &Options) -> anyhow::Result<()> { + let mut src = PathBuf::from(WORKSPACE_ROOT.to_string()); + src.push("test/integration-ebpf/src/bpf"); + + let mut out_path = PathBuf::from(WORKSPACE_ROOT.to_string()); + out_path.push("target"); + out_path.push(opts.target.to_string()); + out_path.push(if opts.release { "release " } else { "debug" }); + + let include_path = out_path.join("include"); + get_libbpf_headers(&opts.libbpf_dir, &include_path)?; + let files = fs::read_dir(&src).unwrap(); + for file in files { + let p = file.unwrap().path(); + if let Some(ext) = p.extension() { + if ext == "c" { + let mut out = PathBuf::from(&out_path); + out.push(p.file_name().unwrap()); + out.set_extension("o"); + compile_with_clang(&p, &out, &include_path)?; + } + } + } + Ok(()) +} + +/// Build eBPF programs with clang and libbpf headers. +fn compile_with_clang>( + src: P, + out: P, + include_path: P, +) -> anyhow::Result<()> { + let clang = match env::var("CLANG") { + Ok(val) => val, + Err(_) => String::from("/usr/bin/clang"), + }; + let arch = match std::env::consts::ARCH { + "x86_64" => "x86", + "aarch64" => "arm64", + _ => std::env::consts::ARCH, + }; + let mut cmd = Command::new(clang); + cmd.arg(format!("-I{}", include_path.as_ref().to_string_lossy())) + .arg("-g") + .arg("-O2") + .arg("-target") + .arg("bpf") + .arg("-c") + .arg(format!("-D__TARGET_ARCH_{}", arch)) + .arg(src.as_ref().as_os_str()) + .arg("-o") + .arg(out.as_ref().as_os_str()); + + let output = cmd.output().context("Failed to execute clang")?; + if !output.status.success() { + bail!( + "Failed to compile eBPF programs\n \ + stdout=\n \ + {}\n \ + stderr=\n \ + {}\n", + String::from_utf8(output.stdout).unwrap(), + String::from_utf8(output.stderr).unwrap() + ); + } + + Ok(()) +} diff --git a/xtask/src/build_test.rs b/xtask/src/build_test.rs new file mode 100644 index 00000000..f2cf91b1 --- /dev/null +++ b/xtask/src/build_test.rs @@ -0,0 +1,29 @@ +use clap::Parser; +use std::process::Command; + +use crate::build_ebpf; + +#[derive(Parser)] +pub struct Options { + /// Whether to compile for the musl libc target + #[clap(short, long)] + pub musl: bool, + + #[clap(flatten)] + pub ebpf_options: build_ebpf::Options, +} + +pub fn build_test(opts: Options) -> anyhow::Result<()> { + build_ebpf::build_ebpf(opts.ebpf_options)?; + + let mut args = vec!["build", "-p", "integration-test", "--verbose"]; + if opts.musl { + args.push("--target=x86_64-unknown-linux-musl"); + } + let status = Command::new("cargo") + .args(&args) + .status() + .expect("failed to build bpf program"); + assert!(status.success()); + Ok(()) +} diff --git a/xtask/src/docs/mod.rs b/xtask/src/docs/mod.rs index e8d1fb73..0496ef63 100644 --- a/xtask/src/docs/mod.rs +++ b/xtask/src/docs/mod.rs @@ -30,13 +30,7 @@ pub fn docs() -> Result<(), anyhow::Error> { header.flush().expect("couldn't flush contents"); let abs_header_path = fs::canonicalize(&header_path).unwrap(); - let args = vec![ - "+nightly", - "doc", - "--workspace", - "--no-deps", - "--all-features", - ]; + let args = vec!["+nightly", "doc", "--no-deps", "--all-features"]; let status = Command::new("cargo") .current_dir(&working_dir) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 406efd9f..5cf9574c 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,5 +1,9 @@ +mod build_ebpf; +mod build_test; mod codegen; mod docs; +mod run; +pub(crate) mod utils; use std::process::exit; @@ -14,6 +18,9 @@ pub struct Options { enum Command { Codegen(codegen::Options), Docs, + BuildIntegrationTest(build_test::Options), + BuildIntegrationTestEbpf(build_ebpf::Options), + IntegrationTest(run::Options), } fn main() { @@ -23,6 +30,9 @@ fn main() { let ret = match opts.command { Codegen(opts) => codegen::codegen(opts), Docs => docs::docs(), + BuildIntegrationTest(opts) => build_test::build_test(opts), + BuildIntegrationTestEbpf(opts) => build_ebpf::build_ebpf(opts), + IntegrationTest(opts) => run::run(opts), }; if let Err(e) = ret { diff --git a/xtask/src/run.rs b/xtask/src/run.rs new file mode 100644 index 00000000..59e24431 --- /dev/null +++ b/xtask/src/run.rs @@ -0,0 +1,72 @@ +use std::{os::unix::process::CommandExt, path::PathBuf, process::Command}; + +use anyhow::Context as _; +use clap::Parser; + +use crate::build_ebpf::{build_ebpf, Architecture, Options as BuildOptions}; + +#[derive(Debug, Parser)] +pub struct Options { + /// Set the endianness of the BPF target + #[clap(default_value = "bpfel-unknown-none", long)] + pub bpf_target: Architecture, + /// Build and run the release target + #[clap(long)] + pub release: bool, + /// The command used to wrap your application + #[clap(short, long, default_value = "sudo -E")] + pub runner: String, + /// libbpf directory + #[clap(long, action)] + pub libbpf_dir: String, + /// Arguments to pass to your application + #[clap(name = "args", last = true)] + pub run_args: Vec, +} + +/// Build the project +fn build(opts: &Options) -> Result<(), anyhow::Error> { + let mut args = vec!["build"]; + if opts.release { + args.push("--release") + } + args.push("--workspace"); + let status = Command::new("cargo") + .args(&args) + .status() + .expect("failed to build userspace"); + assert!(status.success()); + Ok(()) +} + +/// Build and run the project +pub fn run(opts: Options) -> Result<(), anyhow::Error> { + // build our ebpf program followed by our application + build_ebpf(BuildOptions { + target: opts.bpf_target, + release: opts.release, + libbpf_dir: PathBuf::from(&opts.libbpf_dir), + }) + .context("Error while building eBPF program")?; + build(&opts).context("Error while building userspace application")?; + + // profile we are building (release or debug) + let profile = if opts.release { "release" } else { "debug" }; + let bin_path = format!("target/{}/integration-test", profile); + + // arguments to pass to the application + let mut run_args: Vec<_> = opts.run_args.iter().map(String::as_str).collect(); + + // configure args + let mut args: Vec<_> = opts.runner.trim().split_terminator(' ').collect(); + args.push(bin_path.as_str()); + args.append(&mut run_args); + + // spawn the command + let err = Command::new(args.first().expect("No first argument")) + .args(args.iter().skip(1)) + .exec(); + + // we shouldn't get here unless the command failed to spawn + Err(anyhow::Error::from(err).context(format!("Failed to run `{}`", args.join(" ")))) +} diff --git a/xtask/src/utils.rs b/xtask/src/utils.rs new file mode 100644 index 00000000..fcfb918c --- /dev/null +++ b/xtask/src/utils.rs @@ -0,0 +1,17 @@ +use lazy_static::lazy_static; +use serde_json::Value; +use std::process::Command; + +lazy_static! { + pub static ref WORKSPACE_ROOT: String = workspace_root(); +} + +fn workspace_root() -> String { + let output = Command::new("cargo").arg("metadata").output().unwrap(); + if !output.status.success() { + panic!("unable to run cargo metadata") + } + let stdout = String::from_utf8(output.stdout).unwrap(); + let v: Value = serde_json::from_str(&stdout).unwrap(); + v["workspace_root"].as_str().unwrap().to_string() +}