Bladeren bron

Merge pull request #182 from dave-tucker/moar-tests

Regression Test Framework Improvements
Alessandro Decina 3 jaren geleden
bovenliggende
commit
9f711e44fe

+ 3 - 31
.github/workflows/build-aya.yml

@@ -16,7 +16,6 @@ env:
 jobs:
 jobs:
   build:
   build:
     runs-on: ubuntu-20.04
     runs-on: ubuntu-20.04
-
     steps:
     steps:
       - uses: actions/checkout@v2
       - uses: actions/checkout@v2
       - uses: Swatinem/rust-cache@v1
       - uses: Swatinem/rust-cache@v1
@@ -30,41 +29,14 @@ jobs:
   test:
   test:
     runs-on: ubuntu-20.04
     runs-on: ubuntu-20.04
     needs: build
     needs: build
+    container:
+      image: ghcr.io/aya-rs/aya-test-rtf:main
 
 
     steps:
     steps:
       - uses: actions/checkout@v2
       - uses: actions/checkout@v2
 
 
-      - uses: actions-rs/toolchain@v1
-        with:
-          toolchain: nightly
-          components: rustfmt, clippy, rust-src
-          override: true
-          target: x86_64-unknown-linux-musl
-
-      - uses: Swatinem/rust-cache@v1
-
-      - name: Set up Go 1.17
-        uses: actions/setup-go@v2
-        with:
-          go-version: 1.17
-
-      - name: Set GOPATH
-        run: echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
-        env:
-          GOPATH: ${{runner.workspace}}
-
-      - name: Install prereqs
-        run: |
-          go install github.com/linuxkit/rtf@latest
-          cargo install bpf-linker
-          cargo install rust-script
-          cargo install sccache
-          echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
-          export DEBIAN_FRONTEND=noninteractive
-          sudo apt-get update
-          sudo apt-get install -qy qemu-utils qemu-system-x86 cloud-image-utils genisoimage
-
       - name: Run regression tests
       - name: Run regression tests
         run: |
         run: |
+          ln -s /root/.rustup ${HOME}/.rustup
           cd test
           cd test
           rtf -vvv run
           rtf -vvv run

+ 46 - 0
.github/workflows/images.yml

@@ -0,0 +1,46 @@
+name: Aya test image
+
+on:
+  schedule:
+    - cron: "42 2 * * 0"
+  push:
+    branches:
+      - 'main'
+    paths:
+      - 'images/**'
+
+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:
+          path: ./images
+          file: Dockerfile.rtf
+          push: true
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}

+ 38 - 0
images/Dockerfile.rtf

@@ -0,0 +1,38 @@
+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"]

+ 7 - 1
test/README.md

@@ -7,13 +7,19 @@ common usage behaviours work on real Linux distros
 
 
 This assumes you have a working Rust and Go toolchain on the host machine
 This assumes you have a working Rust and Go toolchain on the host machine
 
 
-1. `rustup toolcahin add x86_64-unknown-linux-musl`
+1. `rustup toolchain add x86_64-unknown-linux-musl`
 1. Install [`rtf`](https://github.com/linuxkit/rtf): `go install github.com/linuxkit/rtf@latest`
 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 rust-script: `cargo install rust-script`
 1. Install `qemu` and `cloud-init-utils` package - or any package that provides `cloud-localds`
 1. Install `qemu` and `cloud-init-utils` package - or any package that provides `cloud-localds`
 
 
 It is not required, but the tests run significantly faster if you use `sccache`
 It is not required, but the tests run significantly faster if you use `sccache`
 
 
+You may also use the docker image to run the tests:
+
+```
+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
+```
+
 ## Usage
 ## Usage
 
 
 To read more about how to use `rtf`, see the [documentation](https://github.com/linuxkit/rtf/blob/master/docs/USER_GUIDE.md)
 To read more about how to use `rtf`, see the [documentation](https://github.com/linuxkit/rtf/blob/master/docs/USER_GUIDE.md)

+ 5 - 5
test/cases/000_smoke/000_xdp/test.sh

@@ -11,8 +11,8 @@ set -e
 NAME=pass
 NAME=pass
 
 
 clean_up() {
 clean_up() {
-    rm -rf ebpf user ${NAME}.o ${NAME}
-    exec_vm rm -f pass pass.o
+    rm -rf ${NAME}.o ${NAME}
+    exec_vm rm -f ${NAME} ${NAME}.o
 }
 }
 
 
 trap clean_up EXIT
 trap clean_up EXIT
@@ -21,9 +21,9 @@ trap clean_up EXIT
 compile_ebpf "$(pwd)/${NAME}.ebpf.rs"
 compile_ebpf "$(pwd)/${NAME}.ebpf.rs"
 compile_user "$(pwd)/${NAME}.rs"
 compile_user "$(pwd)/${NAME}.rs"
 
 
-scp_vm pass.o
-scp_vm pass
+scp_vm ${NAME}.o
+scp_vm ${NAME}
 
 
-exec_vm sudo ./pass
+exec_vm sudo ./${NAME}
 
 
 exit 0
 exit 0

+ 11 - 0
test/cases/000_smoke/010_ext/ext.bpf.c

@@ -0,0 +1,11 @@
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+SEC("xdp/drop")
+int xdp_drop(struct xdp_md *ctx)
+{
+    return XDP_DROP;
+}
+
+char _license[] SEC("license") = "GPL";

+ 24 - 0
test/cases/000_smoke/010_ext/ext.rs

@@ -0,0 +1,24 @@
+//! ```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...");
+}

+ 11 - 0
test/cases/000_smoke/010_ext/main.bpf.c

@@ -0,0 +1,11 @@
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+SEC("xdp/pass")
+int xdp_pass(struct xdp_md *ctx)
+{
+    return XDP_PASS;
+}
+
+char _license[] SEC("license") = "GPL";

+ 33 - 0
test/cases/000_smoke/010_ext/test.sh

@@ -0,0 +1,33 @@
+#!/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

+ 85 - 0
test/cases/_lib/compile-ebpf.ers

@@ -0,0 +1,85 @@
+//! ```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<P: AsRef<Path>>(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<P: Clone + AsRef<Path>>(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<String> = 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(())
+}

+ 32 - 0
test/cases/_lib/lib.sh

@@ -9,6 +9,9 @@ AYA_TMPDIR="${RT_PROJECT_ROOT}/_tmp"
 # Directory for VM images
 # Directory for VM images
 AYA_IMGDIR="${RT_PROJECT_ROOT}/_images"
 AYA_IMGDIR="${RT_PROJECT_ROOT}/_images"
 
 
+# Cancel Exit Code
+RT_CANCEL=253
+
 # Test Architecture
 # Test Architecture
 if [ -z "${AYA_TEST_ARCH}" ]; then
 if [ -z "${AYA_TEST_ARCH}" ]; then
     AYA_TEST_ARCH="$(uname -m)"
     AYA_TEST_ARCH="$(uname -m)"
@@ -54,6 +57,17 @@ EOF
     cargo build -q --manifest-path "${dir}/ebpf/Cargo.toml"
     cargo build -q --manifest-path "${dir}/ebpf/Cargo.toml"
     mv "${dir}/ebpf/target/bpfel-unknown-none/debug/${artifact}" "${dir}/${base}.o"
     mv "${dir}/ebpf/target/bpfel-unknown-none/debug/${artifact}" "${dir}/${base}.o"
     rm -rf "${dir}/.cargo"
     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
 # compiles the userspace program by using rust-script to create a temporary
@@ -75,6 +89,7 @@ members = []
 EOF
 EOF
     cargo build -q --release --manifest-path "${dir}/user/Cargo.toml" --target=x86_64-unknown-linux-musl
     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}"
     mv "${dir}/user/target/x86_64-unknown-linux-musl/release/${artifact}" "${dir}/${base}"
+    rm -rf "${dir}/user"
 }
 }
 
 
 download_images() {
 download_images() {
@@ -227,3 +242,20 @@ cleanup_vm() {
         stop_vm
         stop_vm
     fi
     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)
+
+    vm_kernel=$(exec_vm uname -r)
+    vm_major=$(echo "${vm_kernel}" | cut -d '.' -f1)
+    vm_minor=$(echo "${vm_kernel}" | cut -d '.' -f2)
+
+    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
+}